November 1st, 2024

Goal

Consolidated the Multitargeting Analysis and Population Analysis modules. Addressed various bug fixes to improve functionality and user experience.

Hypothesis

If the Multitargeting Analysis and Population Analysis modules are consolidated, users will be able to use these functionalities in CASPER.

If the bugs are resolved, users will be able to operate the program without experiencing crashes.

Expected Results

Results

Bugs Fixed

Special thanks to David for stress-testing the program last week. The following issues have been resolved:

Next Steps

My next step is to address any bugs that David may encounter during testing. I’ll focus on stabilizing the modules with outstanding issues and proceed with the implementation of the Microbiome Analysis module.

Files changed (38) hide show
  1. src/controllers/FindTargetsController.py +93 -25
  2. src/controllers/HomeWindowController.py +26 -6
  3. src/controllers/MainWindowController.py +20 -30
  4. src/controllers/MultitargetingWindowController.py +244 -55
  5. src/controllers/NCBIWindowController.py +17 -117
  6. src/controllers/NewEndonucleaseController.py +0 -1
  7. src/controllers/PopulatioAnalysisWindowController.py +0 -0
  8. src/controllers/PopulationAnalysisWindowController.py +130 -32
  9. src/controllers/ViewTargetsController.py +129 -15
  10. src/models/AnnotationParser.py +130 -36
  11. src/models/CSPRparser.py +121 -19
  12. src/models/DatabaseManager.py +290 -3
  13. src/models/FindTargetsModel.py +62 -28
  14. src/models/GlobalSettings.py +17 -0
  15. src/models/MultitargetingWindowModel.py +105 -113
  16. src/models/NCBIWindowModel.py +120 -0
  17. src/models/NewGenomeWindowModel.py +2 -2
  18. src/models/PopulationAnalysisWindowModel.py +84 -30
  19. src/models/ViewTargetsModel.py +268 -147
  20. src/ui/multitargeting_window.ui +54 -214
  21. src/ui/ncbi_window_v2.ui +0 -10
  22. src/ui/{pop.ui → population_analysis.ui} +134 -270
  23. src/utils/ui.py +8 -19
  24. src/views/AnnotationParser.py +0 -429
  25. src/views/CMainWindow.py +0 -987
  26. src/views/FindTargetsView.py +55 -32
  27. src/views/HomeWindowView.py +3 -0
  28. src/views/MainWindowView.py +208 -51
  29. src/views/MultitargetingWindowView.py +342 -80
  30. src/views/NCBIRenameWindowView.py +3 -1
  31. src/views/NCBIWindowView.py +90 -48
  32. src/views/NewEndonuclease.py +0 -228
  33. src/views/NewGenome.py +0 -705
  34. src/views/PopulationAnalysisWindowView.py +114 -108
  35. src/views/ViewTargetsView.py +48 -9
  36. src/views/closingWin.py +0 -73
  37. src/views/export_tool.py +0 -259
  38. src/views/generateLib.py +0 -662
src/controllers/FindTargetsController.py CHANGED
@@ -1,45 +1,113 @@
1
  from models.FindTargetsModel import FindTargetsModel
2
  from views.FindTargetsView import FindTargetsView
3
  from PyQt6.QtWidgets import QMessageBox
 
4
 
5
  class FindTargetsController:
6
  def __init__(self, global_settings):
7
  self.global_settings = global_settings
8
- self.model = FindTargetsModel(global_settings)
9
- self.view = FindTargetsView(global_settings)
10
- self._connect_signals()
11
  self.organism = None
12
  self.endonuclease = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def _connect_signals(self):
15
- self.view.push_button_view_targets.clicked.connect(self.view_targets)
 
 
16
 
17
  def find_targets(self, input_data):
 
18
  try:
19
- self.global_settings.logger.debug(f"FindTargetsController received input data: {input_data}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  self.organism = input_data['organism']
21
  self.endonuclease = input_data['endonuclease']
 
 
22
  results = self.model.find_targets(input_data)
23
- self.global_settings.logger.debug(f"Find targets results: {results}")
24
- self.view.display_results(results)
25
- except ValueError as e:
26
- error_message = str(e)
27
- QMessageBox.critical(self.view, "Error", error_message)
28
- self.global_settings.logger.error(f"ValueError in find_targets: {error_message}")
 
 
 
 
29
  except Exception as e:
30
- error_message = f"An unexpected error occurred while finding targets: {str(e)}"
31
- QMessageBox.critical(self.view, "Error", error_message)
32
- self.global_settings.logger.error(f"Unexpected error in find_targets: {str(e)}", exc_info=True)
33
 
34
  def view_targets(self):
35
- selected_targets = self.view.get_selected_targets()
36
- if not selected_targets:
37
- QMessageBox.warning(self.view, "No Selection", "Please select targets to view.")
38
- return
39
-
40
- view_targets_controller = self.global_settings.get_view_targets_window()
41
- view_targets_controller.load_targets(selected_targets, self.organism, self.endonuclease)
42
- self.global_settings.main_window.open_new_tab("View Targets", view_targets_controller)
43
-
44
- def show(self):
45
- self.view.show()
 
 
 
 
 
 
 
 
1
  from models.FindTargetsModel import FindTargetsModel
2
  from views.FindTargetsView import FindTargetsView
3
  from PyQt6.QtWidgets import QMessageBox
4
+ from PyQt6.QtCore import QTimer
5
 
6
  class FindTargetsController:
7
  def __init__(self, global_settings):
8
  self.global_settings = global_settings
9
+ self.model = None
10
+ self.view = None
 
11
  self.organism = None
12
  self.endonuclease = None
13
+ self._input_data = None
14
+ self._current_annotation_file = None
15
+
16
+ # Connect to annotation file changes
17
+ self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
18
+
19
+ def _on_annotation_file_changed(self, new_annotation_file):
20
+ """Handle annotation file changes by reprocessing data"""
21
+ try:
22
+ self.global_settings.logger.debug(f"FindTargetsController received new annotation file: {new_annotation_file}")
23
+ self._current_annotation_file = new_annotation_file
24
+
25
+ # Clear existing view and model
26
+ self.view = None
27
+ self.model = None
28
+
29
+ except Exception as e:
30
+ self.global_settings.logger.error(f"Error handling annotation file change: {str(e)}")
31
 
32
  def _connect_signals(self):
33
+ """Connect view signals"""
34
+ if self.view:
35
+ self.view.push_button_view_targets.clicked.connect(self.view_targets)
36
 
37
  def find_targets(self, input_data):
38
+ """Initialize view and process input data"""
39
  try:
40
+ # Get current annotation file
41
+ current_annotation = self.global_settings.get_current_annotation_file()
42
+ input_data['annotation_file'] = current_annotation
43
+ self._current_annotation_file = current_annotation
44
+
45
+ # Always create new instances
46
+ self.model = FindTargetsModel(self.global_settings)
47
+ self.view = FindTargetsView(self.global_settings)
48
+ self._connect_signals()
49
+
50
+ self._input_data = input_data
51
+
52
+ # Find existing Find Targets tab
53
+ main_window = self.global_settings.main_window
54
+ existing_tab = main_window.find_tab_by_title("Find Targets")
55
+
56
+ if existing_tab:
57
+ # Remove the existing tab
58
+ tab_index = main_window.view.tab_widget.indexOf(existing_tab)
59
+ main_window.view.tab_widget.removeTab(tab_index)
60
+
61
+ # Process data and create new tab
62
+ self._process_input_data(input_data)
63
+
64
+ except Exception as e:
65
+ self.global_settings.logger.error(f"Error in find_targets: {str(e)}")
66
+ raise
67
+
68
+ def _process_input_data(self, input_data):
69
+ """Process input data and update view"""
70
+ try:
71
+ if not self.view:
72
+ return
73
+
74
+ self.global_settings.logger.debug(f"FindTargetsController processing input data: {input_data}")
75
  self.organism = input_data['organism']
76
  self.endonuclease = input_data['endonuclease']
77
+
78
+ # Get new results
79
  results = self.model.find_targets(input_data)
80
+ self.global_settings.logger.debug(f"Found {len(results) if results else 0} targets")
81
+
82
+ # Update view with new results
83
+ if results:
84
+ self.view.display_results(results)
85
+
86
+ # Add new tab with updated view
87
+ main_window = self.global_settings.main_window
88
+ main_window.open_new_tab("Find Targets", self)
89
+
90
  except Exception as e:
91
+ self.global_settings.logger.error(f"Error processing input data: {str(e)}")
92
+ if self.view:
93
+ QMessageBox.critical(self.view, "Error", f"An error occurred while processing data: {str(e)}")
94
 
95
  def view_targets(self):
96
+ """Handle view targets button click"""
97
+ try:
98
+ if not self.view:
99
+ return
100
+
101
+ selected_targets = self.view.get_selected_targets()
102
+ if not selected_targets:
103
+ QMessageBox.warning(self.view, "No Selection", "Please select targets to view.")
104
+ return
105
+
106
+ view_targets_controller = self.global_settings.get_view_targets_window()
107
+ view_targets_controller.load_targets(selected_targets, self.organism, self.endonuclease)
108
+ self.global_settings.main_window.open_new_tab("View Targets", view_targets_controller)
109
+
110
+ except Exception as e:
111
+ self.global_settings.logger.error(f"Error in view_targets: {str(e)}")
112
+ if self.view:
113
+ QMessageBox.critical(self.view, "Error", f"An error occurred while viewing targets: {str(e)}")
src/controllers/HomeWindowController.py CHANGED
@@ -78,6 +78,9 @@ class HomeWindowController:
78
  self.view.push_button_find_targets.clicked.connect(self.gather_settings)
79
  self.view.push_button_view_targets.clicked.connect(self.view_results)
80
  self.view.push_button_generate_library.clicked.connect(self.prep_gen_lib)
 
 
 
81
  except Exception as e:
82
  show_error(self.global_settings, "Error setting up connections in HomeWindowController", str(e))
83
 
@@ -134,17 +137,27 @@ class HomeWindowController:
134
 
135
  def open_multitargeting_analysis_module(self):
136
  try:
137
- multitargeting_controller = self.global_settings.get_multitargeting_window()
138
- multitargeting_window = multitargeting_controller.view
139
- self.global_settings.main_window.open_new_tab("Multitargeting Analysis", multitargeting_window)
 
 
 
 
 
140
  except Exception as e:
141
  show_error(self.global_settings, "Error in open_multitargeting_analysis_widget() in Home", str(e))
142
 
143
  def open_population_analysis_module(self):
144
  try:
145
- population_analysis_controller = self.global_settings.get_population_analysis_window()
146
- population_analysis_window = population_analysis_controller.view
147
- self.global_settings.main_window.open_new_tab("Population Analysis", population_analysis_window)
 
 
 
 
 
148
  except Exception as e:
149
  show_error(self.global_settings, "Error in open_population_analysis_widget() in Home", str(e))
150
 
@@ -198,4 +211,11 @@ class HomeWindowController:
198
 
199
  def get_annotation_files(self):
200
  return self.model.get_annotation_files()
 
 
 
 
 
 
 
201
 
 
78
  self.view.push_button_find_targets.clicked.connect(self.gather_settings)
79
  self.view.push_button_view_targets.clicked.connect(self.view_results)
80
  self.view.push_button_generate_library.clicked.connect(self.prep_gen_lib)
81
+
82
+ # Add connection for annotation file changes
83
+ self.view.combo_box_local_annotation_files.currentTextChanged.connect(self._on_annotation_file_changed)
84
  except Exception as e:
85
  show_error(self.global_settings, "Error setting up connections in HomeWindowController", str(e))
86
 
 
137
 
138
  def open_multitargeting_analysis_module(self):
139
  try:
140
+ main_window = self.global_settings.main_window
141
+ existing_tab = main_window.find_tab_by_title("Multitargeting Analysis")
142
+ if existing_tab:
143
+ main_window.view.tab_widget.setCurrentWidget(existing_tab)
144
+ main_window._resize_for_tab("Multitargeting Analysis")
145
+ else:
146
+ multitargeting_controller = self.global_settings.get_multitargeting_window()
147
+ main_window.open_new_tab("Multitargeting Analysis", multitargeting_controller)
148
  except Exception as e:
149
  show_error(self.global_settings, "Error in open_multitargeting_analysis_widget() in Home", str(e))
150
 
151
  def open_population_analysis_module(self):
152
  try:
153
+ main_window = self.global_settings.main_window
154
+ existing_tab = main_window.find_tab_by_title("Population Analysis")
155
+ if existing_tab:
156
+ main_window.view.tab_widget.setCurrentWidget(existing_tab)
157
+ main_window._resize_for_tab("Population Analysis")
158
+ else:
159
+ population_analysis_controller = self.global_settings.get_population_analysis_window()
160
+ main_window.open_new_tab("Population Analysis", population_analysis_controller)
161
  except Exception as e:
162
  show_error(self.global_settings, "Error in open_population_analysis_widget() in Home", str(e))
163
 
 
211
 
212
  def get_annotation_files(self):
213
  return self.model.get_annotation_files()
214
+
215
+ def get_annotation_file(self):
216
+ return self.view.get_annotation_file()
217
+
218
+ def _on_annotation_file_changed(self, new_file):
219
+ """Handle changes to the annotation file selection"""
220
+ self.global_settings.set_current_annotation_file(new_file)
221
 
src/controllers/MainWindowController.py CHANGED
@@ -17,14 +17,12 @@ class MainWindowController:
17
  self.tab_widgets = {} # Store references to tab widgets
18
  self.startup_controller = None
19
  self.is_first_time_startup = self.global_settings.is_first_time_startup
20
- self.tab_sizes = {
21
- "Startup": QSize(750, 550),
22
- "New Genome": QSize(575, 700),
23
- "Home": QSize(1000, 700), # Add a default size for Home tab
24
- "Define New Endonuclease": QSize(450, 600),
25
- "NCBI Download Tool": QSize(1000, 700),
26
- "View Targets": QSize(1500, 700),
27
- }
28
  self.current_tab = None
29
 
30
  try:
@@ -244,25 +242,16 @@ class MainWindowController:
244
  self.view.tab_widget.currentChanged.connect(self._on_tab_changed)
245
 
246
  def _resize_for_tab(self, title):
247
- if title in self.tab_sizes:
248
- new_size = self.tab_sizes[title]
249
- if title == "Startup":
250
- # For Startup tab, set fixed size and disable maximize button
251
- self.view.setFixedSize(new_size)
252
- self.view.setWindowFlags(self.view.windowFlags() & ~Qt.WindowType.WindowMaximizeButtonHint)
253
- else:
254
- # For other tabs, allow resizing but set a minimum size
255
- self.view.setMinimumSize(QSize(400, 300)) # Set a reasonable minimum size
256
- self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
257
- self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
258
-
259
- # Resize to the specified size for the tab
260
- self.view.resize(new_size)
261
  else:
262
- # Default behavior for unknown tabs
263
- self.view.setMinimumSize(QSize(400, 300)) # Set a reasonable minimum size
264
  self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
265
  self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
 
266
 
267
  # Ensure window flags are updated
268
  self.view.show()
@@ -349,16 +338,15 @@ class MainWindowController:
349
  return None
350
 
351
  def _on_tab_changed(self, index):
352
- # Save the current tab size before switching
353
- if self.current_tab:
354
  current_size = self.view.size()
355
  if current_size.width() >= 400 and current_size.height() >= 300:
356
- self.tab_sizes[self.current_tab] = current_size
 
357
 
358
- # Get the new tab title
359
  new_tab_title = self.view.tab_widget.tabText(index)
360
-
361
- # Resize for the new tab
362
  self._resize_for_tab(new_tab_title)
363
 
364
  def close_new_genome_and_switch_to_home(self):
@@ -391,3 +379,5 @@ class MainWindowController:
391
  show_error(self.global_settings, "Error switching to Home tab", str(e))
392
 
393
 
 
 
 
17
  self.tab_widgets = {} # Store references to tab widgets
18
  self.startup_controller = None
19
  self.is_first_time_startup = self.global_settings.is_first_time_startup
20
+
21
+ # Single shared size for all regular tabs
22
+ self.shared_tab_size = QSize(850, 850)
23
+ # Separate size only for startup
24
+ self.startup_size = QSize(750, 550)
25
+
 
 
26
  self.current_tab = None
27
 
28
  try:
 
242
  self.view.tab_widget.currentChanged.connect(self._on_tab_changed)
243
 
244
  def _resize_for_tab(self, title):
245
+ if title == "Startup":
246
+ # For Startup tab, set fixed size and disable maximize button
247
+ self.view.setFixedSize(self.startup_size)
248
+ self.view.setWindowFlags(self.view.windowFlags() & ~Qt.WindowType.WindowMaximizeButtonHint)
 
 
 
 
 
 
 
 
 
 
249
  else:
250
+ # For all other tabs, use the shared size and allow resizing
251
+ self.view.setMinimumSize(QSize(400, 300))
252
  self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
253
  self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
254
+ self.view.resize(self.shared_tab_size)
255
 
256
  # Ensure window flags are updated
257
  self.view.show()
 
338
  return None
339
 
340
  def _on_tab_changed(self, index):
341
+ # Save the current size before switching if it's not the startup tab
342
+ if self.current_tab and self.current_tab != "Startup":
343
  current_size = self.view.size()
344
  if current_size.width() >= 400 and current_size.height() >= 300:
345
+ # Update shared size for all non-startup tabs
346
+ self.shared_tab_size = current_size
347
 
348
+ # Get the new tab title and resize
349
  new_tab_title = self.view.tab_widget.tabText(index)
 
 
350
  self._resize_for_tab(new_tab_title)
351
 
352
  def close_new_genome_and_switch_to_home(self):
 
379
  show_error(self.global_settings, "Error switching to Home tab", str(e))
380
 
381
 
382
+
383
+
src/controllers/MultitargetingWindowController.py CHANGED
@@ -1,67 +1,256 @@
1
  from PyQt6.QtWidgets import QMainWindow
2
  from views.MultitargetingWindowView import MultitargetingWindowView
3
  from models.MultitargetingWindowModel import MultitargetingWindowModel
 
4
 
5
  class MultitargetingWindowController(QMainWindow):
6
  def __init__(self, global_settings):
7
  super().__init__()
8
- self.global_settings = global_settings
9
- self._view = MultitargetingWindowView(global_settings)
10
- self._model = MultitargetingWindowModel(global_settings)
11
- self.setCentralWidget(self._view)
12
- self.setup_connections()
13
-
14
- @property
15
- def view(self):
16
- return self._view
17
-
18
- @property
19
- def model(self):
20
- return self._model
21
-
22
- def show(self):
23
- super().show()
24
-
25
- def setup_connections(self):
26
- # Connect signals from view to controller methods
27
- self.view.comboBox_organism.currentIndexChanged.connect(self.update_endo_list)
28
- self.view.comboBox_endo.currentIndexChanged.connect(self.load_data)
29
- self.view.pushButton_load.clicked.connect(self.load_data)
30
- # Add more connections as needed
31
-
32
- def update_endo_list(self):
33
- organism = self.view.comboBox_organism.currentText()
34
- endos = self.model.get_endos_for_organism(organism)
35
- self.view.comboBox_endo.clear()
36
- self.view.comboBox_endo.addItems(endos)
37
-
38
- def load_data(self):
39
- organism = self.view.comboBox_organism.currentText()
40
- endo = self.view.comboBox_endo.currentText()
41
- self.model.set_files(organism, endo)
42
 
43
- # Load and process data
44
- repeats_data = self.model.get_repeats_data()
45
- kstats = self.model.get_kstats()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
- # Update view with data
48
- self.view.update_table(repeats_data)
49
- self.view.update_global_stats(self.model)
 
50
 
51
- # Load and update graphs
52
- self.load_graphs()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- def load_graphs(self):
55
- # Load data for graphs
56
- seeds_vs_repeats = self.model.get_seeds_vs_repeats_data()
57
- repeats_vs_seeds = self.model.get_repeats_vs_seeds_data()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- # Update graphs in view
60
- self.view.update_global_line_graph(seeds_vs_repeats)
61
- self.view.update_global_bar_graph(repeats_vs_seeds)
62
-
63
- def initialize(self):
64
- organisms = self.model.get_organisms()
65
- self.view.comboBox_organism.addItems(organisms)
66
- self.view.scaleUI()
67
- self.view.centerUI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from PyQt6.QtWidgets import QMainWindow
2
  from views.MultitargetingWindowView import MultitargetingWindowView
3
  from models.MultitargetingWindowModel import MultitargetingWindowModel
4
+ from utils.ui import show_error
5
 
6
  class MultitargetingWindowController(QMainWindow):
7
  def __init__(self, global_settings):
8
  super().__init__()
9
+ self.settings = global_settings
10
+ self.logger = global_settings.get_logger()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ try:
13
+ self._model = MultitargetingWindowModel(global_settings)
14
+ self._view = MultitargetingWindowView(global_settings)
15
+ self.setCentralWidget(self._view)
16
+
17
+ self._init_ui()
18
+ self._setup_connections()
19
+ except Exception as e:
20
+ show_error(self.settings, "Error initializing MultitargetingWindowController", str(e))
21
+
22
+ def _init_ui(self):
23
+ """Initialize UI components"""
24
+ try:
25
+ # Set up initial data
26
+ organisms = self._model.get_organisms()
27
+
28
+ self._view.combo_box_organism.clear()
29
+ self._view.combo_box_organism.addItems(organisms)
30
+
31
+ # If we have organisms, trigger the first one
32
+ if organisms:
33
+ self._on_organism_changed(0)
34
+
35
+ # Initialize plots
36
+ self._view.setup_plots()
37
+
38
+ except Exception as e:
39
+ self.logger.error(f"Error in _init_ui: {str(e)}")
40
+ show_error(self.settings, "Error", f"Failed to initialize UI: {str(e)}")
41
+
42
+ def _setup_connections(self):
43
+ """Set up signal-slot connections"""
44
+ # Organism and endonuclease selection
45
+ self._view.combo_box_organism.currentIndexChanged.connect(self._on_organism_changed)
46
+ self._view.combo_box_endonuclease.currentIndexChanged.connect(self._on_endonuclease_changed)
47
 
48
+ # Buttons
49
+ self._view.push_button_analyze.clicked.connect(self._on_analyze_clicked)
50
+ self._view.push_button_statistics_overview.clicked.connect(self._on_statistics_overview_clicked)
51
+ self._view.tool_button_sql_settings.clicked.connect(self._on_sql_settings_clicked)
52
 
53
+ # Table selection
54
+ self._view.table_seeds.itemSelectionChanged.connect(self._on_seed_selected)
55
+ self._view.check_box_select_all.stateChanged.connect(self._on_select_all_changed)
56
+
57
+ def _on_organism_changed(self, index):
58
+ """Handle organism selection change"""
59
+ try:
60
+ organism = self._view.combo_box_organism.currentText()
61
+ if not organism:
62
+ return
63
+
64
+ # Get endos for selected organism
65
+ endos = self._model.get_endos_for_organism(organism)
66
+
67
+ # Update endonuclease combo box
68
+ self._view.combo_box_endonuclease.clear()
69
+ self._view.combo_box_endonuclease.addItems(endos)
70
+
71
+ # Update file paths
72
+ if endos:
73
+ self._model.set_files(organism, endos[0])
74
+
75
+ except Exception as e:
76
+ self.logger.error(f"Error in _on_organism_changed: {str(e)}")
77
+ show_error(self.settings, "Error", f"Failed to update endonucleases: {str(e)}")
78
+
79
+ def _on_endonuclease_changed(self, index):
80
+ """Handle endonuclease selection change"""
81
+ self._update_analysis_button_state()
82
+
83
+ def _on_analyze_clicked(self):
84
+ """Handle analyze button click"""
85
+ try:
86
+ organism = self._view.combo_box_organism.currentText()
87
+ endo = self._view.combo_box_endonuclease.currentText()
88
+
89
+ if not organism or not endo:
90
+ show_error(self.settings, "Analysis Error", "Please select both an organism and an endonuclease.")
91
+ return
92
+
93
+ # Load data
94
+ try:
95
+ self._model.set_files(organism, endo)
96
+ except FileNotFoundError as e:
97
+ show_error(self.settings, "File Error",
98
+ f"Could not find required files for {organism} with {endo}. Please ensure the files exist.")
99
+ return
100
+ except ValueError as e:
101
+ show_error(self.settings, "Input Error", str(e))
102
+ return
103
+
104
+ seeds_data = self._model.get_repeats_data()
105
+
106
+ # Update UI
107
+ self._view.update_seeds_table(seeds_data)
108
+ self._update_plots()
109
+
110
+ except Exception as e:
111
+ show_error(self.settings, "Analysis Error", str(e))
112
+
113
+ def _on_statistics_overview_clicked(self):
114
+ """Handle statistics overview button click"""
115
+ try:
116
+ stats = self._model.calculate_statistics()
117
+ self._show_statistics_dialog(stats)
118
+ except Exception as e:
119
+ show_error(self.settings, "Statistics Error", str(e))
120
+
121
+ def _on_sql_settings_clicked(self):
122
+ """Handle SQL settings button click"""
123
+ try:
124
+ current_settings = self._model.get_sql_settings()
125
+ if self._show_sql_settings_dialog(current_settings):
126
+ new_settings = self._get_sql_settings_from_dialog()
127
+ self._model.update_sql_settings(new_settings)
128
+ except Exception as e:
129
+ show_error(self.settings, "SQL Settings Error", str(e))
130
+
131
+ def _on_seed_selected(self):
132
+ """Handle seed selection in table"""
133
+ try:
134
+ selected_items = self._view.table_seeds.selectedItems()
135
+ if selected_items:
136
+ row = selected_items[0].row()
137
+ seed = self._view.table_seeds.item(row, 0).text()
138
+
139
+ # Get seed data
140
+ seed_data = self._model.get_seed_data(seed)
141
+ if not seed_data:
142
+ return
143
 
144
+ # Process seed data for visualization
145
+ kstats = self._model.get_kstats()
146
+ seed_data_processed, event_data = self._process_seed_data(seed_data, kstats)
147
+
148
+ # Update chromosome viewer
149
+ self._view.fill_chromosome_viewer(seed_data_processed, event_data)
150
+
151
+ # Update only the chromosome bar plot
152
+ chromosome_data = self._model.get_chro_bar_data(seed)
153
+ # Update only the chromosome plot, keep other plots unchanged
154
+ self._view._update_repeat_vs_chromosome_plot(chromosome_data)
155
+
156
+ except Exception as e:
157
+ self.logger.error(f"Error handling seed selection: {str(e)}")
158
+ show_error(self.settings, "Error", f"Failed to display seed data: {str(e)}")
159
+
160
+ def _process_seed_data(self, seed_data, kstats):
161
+ """Process seed data for visualization"""
162
+ try:
163
+ seed_data_processed = {}
164
+ event_data = {}
165
+
166
+ for data in seed_data:
167
+ # Split chromosome and location strings into lists
168
+ chromos = [int(x) for x in data[0].split(',')]
169
+ locs = [int(x) for x in data[1].split(',')]
170
+ pams = data[2].split(',')
171
+ scores = data[3].split(',')
172
+ fives = data[4].split(',')
173
+ threes = data[5].split(',')
174
+
175
+ # Process each chromosome location
176
+ for i in range(len(chromos)):
177
+ chromo = chromos[i]
178
+ pos = locs[i]
179
+
180
+ # Normalize location
181
+ dir = "+" if pos >= 0 else "-"
182
+ normalized_location = abs(float(pos) / float(kstats[chromo - 1]))
183
+
184
+ # Store data
185
+ if chromo in seed_data_processed:
186
+ seed_data_processed[chromo].append(normalized_location)
187
+ event_data[chromo].append([
188
+ normalized_location,
189
+ pos,
190
+ fives[i] + threes[i],
191
+ pams[i],
192
+ scores[i],
193
+ dir
194
+ ])
195
+ else:
196
+ seed_data_processed[chromo] = [normalized_location]
197
+ event_data[chromo] = [[
198
+ normalized_location,
199
+ pos,
200
+ fives[i] + threes[i],
201
+ pams[i],
202
+ scores[i],
203
+ dir
204
+ ]]
205
+
206
+ return seed_data_processed, event_data
207
+
208
+ except Exception as e:
209
+ self.logger.error(f"Error processing seed data: {str(e)}")
210
+ raise
211
+
212
+ def _on_select_all_changed(self, state):
213
+ """Handle select all checkbox state change"""
214
+ self._view.table_seeds.selectAll() if state else self._view.table_seeds.clearSelection()
215
+
216
+ def _update_analysis_button_state(self):
217
+ """Update analyze button enabled state"""
218
+ has_organism = bool(self._view.organism_drop.currentText())
219
+ has_endo = bool(self._view.combo_box_endonuclease.currentText())
220
+ self._view.push_button_analyze.setEnabled(has_organism and has_endo)
221
 
222
+ # Clear any existing data if selection changes
223
+ if not (has_organism and has_endo):
224
+ self._view.table_seeds.setRowCount(0)
225
+ self._view.update_plots(None, None, None)
226
+
227
+ def _update_plots(self):
228
+ """Update all plots with current data"""
229
+ try:
230
+ # Get repeats vs seeds data first
231
+ repeats_data = self._model.get_repeats_vs_seeds_data()
232
+
233
+ # Get sequences vs repeats data
234
+ sequences_data = self._model.get_seeds_vs_repeats_data()
235
+
236
+ # Update all plots at once
237
+ self._view.update_plots(repeats_data, sequences_data, None) # chromosome_data will be updated on seed selection
238
+
239
+ except Exception as e:
240
+ self.logger.error(f"Error in _update_plots: {str(e)}")
241
+ show_error(self.settings, "Plot Update Error", str(e))
242
+
243
+ def _show_statistics_dialog(self, stats):
244
+ """Show statistics overview dialog"""
245
+ # Implement statistics dialog display
246
+ pass
247
+
248
+ def _show_sql_settings_dialog(self, current_settings):
249
+ """Show SQL settings dialog"""
250
+ # Implement SQL settings dialog display
251
+ return False
252
+
253
+ def _get_sql_settings_from_dialog(self):
254
+ """Get settings from SQL settings dialog"""
255
+ # Implement getting settings from dialog
256
+ return {}
src/controllers/NCBIWindowController.py CHANGED
@@ -18,18 +18,22 @@ class NCBIWindowController:
18
  self.model = NCBIWindowModel(settings)
19
  self.view = NCBIWindowView(settings)
20
 
 
 
 
21
  self._init_ui()
22
- self.setup_connections()
23
  except Exception as e:
24
  show_error(self.settings, "Error initializing NCBIWindowController", str(e))
25
 
26
  def setup_connections(self):
 
27
  try:
28
  self.view.push_button_search.clicked.connect(self.search_ncbi)
29
  self.view.push_button_download_files.clicked.connect(self.download_files_wrapper)
30
  self.view.check_box_select_all_rows.clicked.connect(self.select_all_rows_in_table)
31
  self.view.radio_button_collections_genbank.toggled.connect(self.is_checked_GenBank_radio_button)
32
-
 
33
  except Exception as e:
34
  self.logger.error(f"Error setting up connections: {str(e)}", exc_info=True)
35
  show_error(self.settings, "Error setting up connections", str(e))
@@ -135,7 +139,9 @@ class NCBIWindowController:
135
  self.on_thread_completed() # Increment completed threads for unavailable files
136
  continue
137
 
138
- downloader = DownloadThread(self, url, id, species_name, strain, self.view.check_box_file_types_fna.isChecked(), self.view.check_box_file_types_gbff.isChecked())
 
 
139
  downloader.finished.connect(self.on_download_finished)
140
  downloader.progress_updated.connect(self.update_progress)
141
  downloader.status_updated.connect(self.update_status)
@@ -165,7 +171,7 @@ class NCBIWindowController:
165
  self.rename_files(self.model.files)
166
  elif not self.unavailable_files:
167
  show_message(12, QtWidgets.QMessageBox.Icon.Warning, "No Files Downloaded", "No files were downloaded. Please check your selection and try again.")
168
-
169
  def show_unavailable_files_warning(self):
170
  warning_text = "The following files were not available for download:\n\n"
171
  for species_name, strain in self.unavailable_files:
@@ -209,22 +215,18 @@ class NCBIWindowController:
209
 
210
  def on_rename_complete(self):
211
  try:
212
- # self.update_main_window()
213
  self.view.set_download_files_status_label("Download and renaming complete.<br>Press Back to go back to the Main window.")
 
 
 
 
 
 
 
214
  except Exception as e:
215
  self.logger.error(f"Error in on_rename_complete: {str(e)}", exc_info=True)
216
  show_error(self.settings, "Error after renaming", str(e))
217
 
218
- def update_main_window(self):
219
- try:
220
- if hasattr(self.main_window, 'fill_annotation_dropdown'):
221
- self.main_window.fill_annotation_dropdown()
222
- else:
223
- self.logger.warning("MainWindowController does not have fill_annotation_dropdown method")
224
- except Exception as e:
225
- self.logger.error(f"Error updating main window: {str(e)}", exc_info=True)
226
- show_error(self.settings, "Error updating main window", str(e))
227
-
228
  def select_all_rows_in_table(self):
229
  if self.view.check_box_select_all_rows.isChecked():
230
  self.view.table_ncbi_results.selectAll()
@@ -239,105 +241,3 @@ class NCBIWindowController:
239
  show_message("Warning!",
240
  "The GenBank collection may contain poorly or partially annotated annotation files. "
241
  "We highly recommend using the RefSeq collection if it is available.")
242
-
243
- class DownloadThread(QtCore.QThread):
244
- finished = QtCore.pyqtSignal(bool)
245
- progress_updated = QtCore.pyqtSignal(int, int, int)
246
- status_updated = QtCore.pyqtSignal(str)
247
- all_completed = QtCore.pyqtSignal() # New signal to indicate all operations are complete
248
-
249
- def __init__(self, controller, url, id, species_name, strain, download_fna, download_gbff):
250
- super().__init__()
251
- self.controller = controller
252
- self.url = url
253
- self.id = id
254
- self.species_name = species_name
255
- self.strain = strain
256
- self.download_fna = download_fna
257
- self.download_gbff = download_gbff
258
-
259
- def run(self):
260
- try:
261
- parsed_url = urlparse(self.url)
262
- ftp_host = parsed_url.netloc
263
- ftp_path = parsed_url.path
264
-
265
- self.controller.logger.info(f"Attempting to connect to FTP server: {ftp_host}")
266
-
267
- # Resolve the IP address
268
- try:
269
- ip_address = socket.gethostbyname(ftp_host)
270
- self.controller.logger.info(f"Resolved IP address: {ip_address}")
271
- except socket.gaierror as e:
272
- self.controller.logger.error(f"Failed to resolve hostname: {ftp_host}. Error: {str(e)}")
273
- self.finished.emit(False)
274
- return
275
-
276
- ftp = FTP(ftp_host)
277
- ftp.login()
278
- ftp.cwd(ftp_path)
279
- ftp.set_pasv(True) # Use passive mode
280
- ftp.voidcmd('TYPE I') # Set binary mode
281
-
282
- self.controller.logger.info(f"Successfully connected to FTP server: {ftp_host}")
283
-
284
- files_to_download = []
285
- if self.download_fna:
286
- fna_files = [f for f in ftp.nlst() if f.endswith('_genomic.fna.gz')]
287
- self.controller.logger.info(f"Found {len(fna_files)} FNA files")
288
- files_to_download.extend(fna_files)
289
- if self.download_gbff:
290
- gbff_files = [f for f in ftp.nlst() if f.endswith('_genomic.gbff.gz')]
291
- self.controller.logger.info(f"Found {len(gbff_files)} GBFF files")
292
- files_to_download.extend(gbff_files)
293
-
294
- self.controller.logger.info(f"Total files to download: {len(files_to_download)}")
295
-
296
- total_size = 0
297
- for file in files_to_download:
298
- try:
299
- total_size += ftp.size(file)
300
- except Exception as e:
301
- self.controller.logger.warning(f"Could not get size for file {file}: {str(e)}")
302
-
303
- downloaded_size = 0
304
-
305
- for file in files_to_download:
306
- self.status_updated.emit(f"Downloading: {file}")
307
- file_type = 'FNA' if file.endswith('.fna.gz') else 'GBFF'
308
- output_dir = os.path.join(self.controller.settings.CSPR_DB, file_type)
309
-
310
- # Create the output directory if it doesn't exist
311
- os.makedirs(output_dir, exist_ok=True)
312
-
313
- local_filename = os.path.join(output_dir, file)
314
-
315
- self.controller.logger.info(f"Downloading file: {file} to {local_filename}")
316
-
317
- with open(local_filename, 'wb') as local_file:
318
- def callback(data):
319
- local_file.write(data)
320
- nonlocal downloaded_size
321
- downloaded_size += len(data)
322
- if total_size > 0:
323
- self.progress_updated.emit(self.id, downloaded_size, total_size)
324
-
325
- ftp.retrbinary(f"RETR {file}", callback)
326
-
327
- self.controller.logger.info(f"Download complete: {file}")
328
- self.status_updated.emit(f"Decompressing: {file}")
329
-
330
- # Decompress the file
331
- self.controller.model.decompress_file(local_filename)
332
-
333
- # Add the decompressed file to the model's files list
334
- decompressed_filename = local_filename[:-3] # Remove .gz extension
335
- self.controller.model.add_downloaded_file(decompressed_filename)
336
-
337
- ftp.quit()
338
- self.controller.logger.info(f"All files downloaded and decompressed successfully for ID: {self.id}")
339
- self.all_completed.emit() # Emit the new signal when everything is done
340
- self.finished.emit(True)
341
- except Exception as e:
342
- self.controller.logger.error(f"Download error for ID {self.id}: {str(e)}", exc_info=True)
343
- self.finished.emit(False)
 
18
  self.model = NCBIWindowModel(settings)
19
  self.view = NCBIWindowView(settings)
20
 
21
+ # Connect to the initialization complete signal
22
+ self.view.initialization_complete.connect(self.setup_connections)
23
+
24
  self._init_ui()
 
25
  except Exception as e:
26
  show_error(self.settings, "Error initializing NCBIWindowController", str(e))
27
 
28
  def setup_connections(self):
29
+ """Set up connections after UI is fully initialized"""
30
  try:
31
  self.view.push_button_search.clicked.connect(self.search_ncbi)
32
  self.view.push_button_download_files.clicked.connect(self.download_files_wrapper)
33
  self.view.check_box_select_all_rows.clicked.connect(self.select_all_rows_in_table)
34
  self.view.radio_button_collections_genbank.toggled.connect(self.is_checked_GenBank_radio_button)
35
+
36
+ self.logger.debug("NCBI Window connections setup completed")
37
  except Exception as e:
38
  self.logger.error(f"Error setting up connections: {str(e)}", exc_info=True)
39
  show_error(self.settings, "Error setting up connections", str(e))
 
139
  self.on_thread_completed() # Increment completed threads for unavailable files
140
  continue
141
 
142
+ downloader = self.model.DownloadThread(self, url, id, species_name, strain,
143
+ self.view.check_box_file_types_fna.isChecked(),
144
+ self.view.check_box_file_types_gbff.isChecked())
145
  downloader.finished.connect(self.on_download_finished)
146
  downloader.progress_updated.connect(self.update_progress)
147
  downloader.status_updated.connect(self.update_status)
 
171
  self.rename_files(self.model.files)
172
  elif not self.unavailable_files:
173
  show_message(12, QtWidgets.QMessageBox.Icon.Warning, "No Files Downloaded", "No files were downloaded. Please check your selection and try again.")
174
+
175
  def show_unavailable_files_warning(self):
176
  warning_text = "The following files were not available for download:\n\n"
177
  for species_name, strain in self.unavailable_files:
 
215
 
216
  def on_rename_complete(self):
217
  try:
 
218
  self.view.set_download_files_status_label("Download and renaming complete.<br>Press Back to go back to the Main window.")
219
+
220
+ # Just trigger the database state update
221
+ self.settings.update_db_state()
222
+
223
+ # No need to manually refresh the Home tab as it will receive the db_state_updated signal
224
+ self.logger.info("Database state update triggered after NCBI download")
225
+
226
  except Exception as e:
227
  self.logger.error(f"Error in on_rename_complete: {str(e)}", exc_info=True)
228
  show_error(self.settings, "Error after renaming", str(e))
229
 
 
 
 
 
 
 
 
 
 
 
230
  def select_all_rows_in_table(self):
231
  if self.view.check_box_select_all_rows.isChecked():
232
  self.view.table_ncbi_results.selectAll()
 
241
  show_message("Warning!",
242
  "The GenBank collection may contain poorly or partially annotated annotation files. "
243
  "We highly recommend using the RefSeq collection if it is available.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/NewEndonucleaseController.py CHANGED
@@ -25,7 +25,6 @@ class NewEndonucleaseController:
25
  self.view.push_button_reset_form.clicked.connect(self._handle_reset_form)
26
  self.view.push_button_delete.clicked.connect(self._handle_delete)
27
  self.view.combo_box_select_endonuclease.currentIndexChanged.connect(self._handle_endonuclease_selection)
28
- self.view.show()
29
  except Exception as e:
30
  show_error(self.settings, "Error setting up connections in NewEndonucleaseController", str(e))
31
 
 
25
  self.view.push_button_reset_form.clicked.connect(self._handle_reset_form)
26
  self.view.push_button_delete.clicked.connect(self._handle_delete)
27
  self.view.combo_box_select_endonuclease.currentIndexChanged.connect(self._handle_endonuclease_selection)
 
28
  except Exception as e:
29
  show_error(self.settings, "Error setting up connections in NewEndonucleaseController", str(e))
30
 
src/controllers/PopulatioAnalysisWindowController.py DELETED
File without changes
src/controllers/PopulationAnalysisWindowController.py CHANGED
@@ -2,58 +2,85 @@ from PyQt6 import QtWidgets
2
  from utils.ui import show_error, show_message, position_window
3
  from views.PopulationAnalysisWindowView import PopulationAnalysisWindowView
4
  from models.PopulationAnalysisWindowModel import PopulationAnalysisWindowModel
 
5
 
6
  class PopulationAnalysisWindowController:
7
  def __init__(self, global_settings):
8
  self.global_settings = global_settings
9
- self.model = PopulationAnalysisWindowModel(global_settings)
10
- self.view = PopulationAnalysisWindowView(global_settings)
 
11
  self.setup_connections()
12
 
13
  def setup_connections(self):
14
- self.view.goBackButton.clicked.connect(self.go_back)
15
- self.view.analyze_button.clicked.connect(self.pre_analyze)
16
- self.view.clear_Button.clicked.connect(self.clear)
17
- self.view.export_button.clicked.connect(self.export_tool)
18
- self.view.find_locs_button.clicked.connect(self.find_locations)
19
- self.view.clear_loc_button.clicked.connect(self.clear_loc_table)
20
- self.view.query_seed_button.clicked.connect(self.custom_seed_search)
21
- self.view.endoBox.currentIndexChanged.connect(self.change_endo)
22
- self.view.table2.horizontalHeader().sectionClicked.connect(self.table2_sorting)
23
- self.view.loc_finder_table.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
 
 
 
 
 
 
 
 
 
24
 
25
  def launch(self):
26
  try:
 
 
27
  self.get_data()
28
- self.view.show()
29
  except Exception as e:
 
30
  show_error(self.global_settings, "Error in launch() in population analysis.", str(e))
31
 
32
  def get_data(self):
33
  try:
 
34
  self.fillEndo()
35
  except Exception as e:
 
36
  show_error(self.global_settings, "Error in get_data() in population analysis.", str(e))
37
 
38
  def fillEndo(self):
39
  try:
 
40
  endos = self.model.load_endonucleases()
 
 
 
 
 
 
 
 
41
  self.view.update_endo_dropdown(endos.keys())
42
  self.change_endo()
43
  except Exception as e:
 
44
  show_error(self.global_settings, "Error in fillEndo() in population analysis.", str(e))
45
 
46
  def change_endo(self):
47
  try:
48
  selected_endo = self.view.get_selected_endo()
 
 
49
  org_files = self.model.get_organism_files(selected_endo)
50
  self.view.update_org_table(org_files)
51
  except Exception as e:
52
- show_error(self.global_settings, "Error in change_endo() in population analysis.", str(e))
53
 
54
  def pre_analyze(self):
55
  try:
56
- selected_indexes = self.view.get_selected_organisms()
57
  if len(selected_indexes) == 0:
58
  show_message(
59
  fontSize=12,
@@ -73,37 +100,104 @@ class PopulationAnalysisWindowController:
73
 
74
  def fill_data(self):
75
  try:
76
- self.view.show_loading_window(5)
77
  self.model.seeds = self.model.get_shared_seeds(self.model.db_files, True)
78
 
79
  if len(self.model.seeds) == 0:
80
- self.view.hide_loading_window()
81
  return
82
 
83
  seed_data = []
84
  for seed in self.model.seeds:
85
  data = self.model.get_seed_data(seed, self.model.db_files)
86
- seed_data.append(self.process_seed_data(seed, data))
 
 
87
 
88
- self.view.update_shared_seeds_table(seed_data)
89
-
90
- if len(self.model.db_files) > 1:
91
- heatmap_data = self.model.get_heatmap_data(self.model.db_files)
92
- self.view.plot_heatmap(heatmap_data, self.model.org_names)
 
 
 
93
 
94
- self.view.hide_loading_window()
95
  except Exception as e:
96
  show_error(self.global_settings, "Error in fill_data() in population analysis.", str(e))
97
 
98
  def process_seed_data(self, seed, data):
99
- # Process the seed data and return a list of values for the table
100
- # This method should contain the logic to calculate percentages, averages, etc.
101
- # Return a list that matches the columns in the shared seeds table
102
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
  def custom_seed_search(self):
105
  try:
106
- seeds = self.view.get_seed_input().split(',')
107
  seeds = [seed.strip().upper() for seed in seeds if seed.strip()]
108
 
109
  if not seeds:
@@ -124,7 +218,7 @@ class PopulationAnalysisWindowController:
124
  )
125
  return
126
 
127
- self.view.update_shared_seeds_table(seed_data)
128
  except Exception as e:
129
  show_error(self.global_settings, "Error in custom_seed_search() in population analysis.", str(e))
130
 
@@ -140,6 +234,10 @@ class PopulationAnalysisWindowController:
140
  )
141
  return
142
 
 
 
 
 
143
  locations = self.model.get_seed_locations(selected_seeds, self.model.db_files)
144
  self.view.update_loc_finder_table(locations)
145
  except Exception as e:
@@ -151,11 +249,11 @@ class PopulationAnalysisWindowController:
151
  except Exception as e:
152
  show_error(self.global_settings, "Error in clear_loc_table() in population analysis.", str(e))
153
 
154
- def table2_sorting(self, logicalIndex):
155
  try:
156
  self.view.sort_table2(logicalIndex)
157
  except Exception as e:
158
- show_error(self.global_settings, "Error in table2_sorting() in population analysis.", str(e))
159
 
160
  def loc_table_sorter(self, logicalIndex):
161
  try:
 
2
  from utils.ui import show_error, show_message, position_window
3
  from views.PopulationAnalysisWindowView import PopulationAnalysisWindowView
4
  from models.PopulationAnalysisWindowModel import PopulationAnalysisWindowModel
5
+ import logging
6
 
7
  class PopulationAnalysisWindowController:
8
  def __init__(self, global_settings):
9
  self.global_settings = global_settings
10
+ self.model = PopulationAnalysisWindowModel(self.global_settings)
11
+ self.view = PopulationAnalysisWindowView(self.global_settings)
12
+ self._init_ui()
13
  self.setup_connections()
14
 
15
  def setup_connections(self):
16
+ try:
17
+ # Group: Select Organisms
18
+ self.view.combo_box_endonuclease.currentIndexChanged.connect(self.change_endo)
19
+ self.view.push_button_analyze_organism.clicked.connect(self.pre_analyze)
20
+
21
+ # Group: Seed Analysis
22
+ self.view.push_button_query_seed.clicked.connect(self.custom_seed_search)
23
+ self.view.push_button_clear_seeds.clicked.connect(self.clear)
24
+ self.view.push_button_find_locations.clicked.connect(self.find_locations)
25
+ self.view.push_button_clear_locations.clicked.connect(self.clear_loc_table)
26
+
27
+ # Tables sorting
28
+ self.view.table_seed.horizontalHeader().sectionClicked.connect(self.seed_table_sorting)
29
+ self.view.table_locations.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
30
+ except Exception as e:
31
+ show_error(self.global_settings, "Error setting up connections in population analysis.", str(e))
32
+
33
+ def _init_ui(self):
34
+ self.launch()
35
 
36
  def launch(self):
37
  try:
38
+ self.logger = self.global_settings.get_logger()
39
+ self.logger.info("Launching Population Analysis Window")
40
  self.get_data()
 
41
  except Exception as e:
42
+ self.logger.error(f"Error in launch(): {str(e)}")
43
  show_error(self.global_settings, "Error in launch() in population analysis.", str(e))
44
 
45
  def get_data(self):
46
  try:
47
+ self.logger.info("Getting data for Population Analysis")
48
  self.fillEndo()
49
  except Exception as e:
50
+ self.logger.error(f"Error in get_data(): {str(e)}")
51
  show_error(self.global_settings, "Error in get_data() in population analysis.", str(e))
52
 
53
  def fillEndo(self):
54
  try:
55
+ self.logger.info("Starting fillEndo()")
56
  endos = self.model.load_endonucleases()
57
+ self.logger.debug(f"Loaded endonucleases: {endos}")
58
+
59
+ if not endos:
60
+ self.logger.warning("No endonucleases found")
61
+ show_error(self.global_settings, "Error", "No endonucleases found")
62
+ return
63
+
64
+ self.logger.info(f"Updating dropdown with {len(endos)} endonucleases")
65
  self.view.update_endo_dropdown(endos.keys())
66
  self.change_endo()
67
  except Exception as e:
68
+ self.logger.error(f"Error in fillEndo(): {str(e)}")
69
  show_error(self.global_settings, "Error in fillEndo() in population analysis.", str(e))
70
 
71
  def change_endo(self):
72
  try:
73
  selected_endo = self.view.get_selected_endo()
74
+ if not selected_endo:
75
+ return
76
  org_files = self.model.get_organism_files(selected_endo)
77
  self.view.update_org_table(org_files)
78
  except Exception as e:
79
+ show_error(self.settings, "Error in change_endo() in population analysis.", str(e))
80
 
81
  def pre_analyze(self):
82
  try:
83
+ selected_indexes = [index.row() for index in self.view.table_organism.selectionModel().selectedRows()]
84
  if len(selected_indexes) == 0:
85
  show_message(
86
  fontSize=12,
 
100
 
101
  def fill_data(self):
102
  try:
 
103
  self.model.seeds = self.model.get_shared_seeds(self.model.db_files, True)
104
 
105
  if len(self.model.seeds) == 0:
 
106
  return
107
 
108
  seed_data = []
109
  for seed in self.model.seeds:
110
  data = self.model.get_seed_data(seed, self.model.db_files)
111
+ processed_data = self.process_seed_data(seed, data)
112
+ if processed_data: # Only add if data was processed successfully
113
+ seed_data.append(processed_data)
114
 
115
+ if seed_data: # Only update table if we have data
116
+ self.view.update_shared_seeds_table(seed_data)
117
+
118
+ if len(self.model.db_files) > 1:
119
+ heatmap_data = self.model.get_heatmap_data(self.model.db_files)
120
+ self.view.plot_heatmap(heatmap_data, self.model.org_names)
121
+ else:
122
+ self.logger.warning("No seed data was processed successfully")
123
 
 
124
  except Exception as e:
125
  show_error(self.global_settings, "Error in fill_data() in population analysis.", str(e))
126
 
127
  def process_seed_data(self, seed, data):
128
+ """Process seed data and return a tuple of values for the table"""
129
+ try:
130
+ # self.logger.debug(f"Processing seed data: {data}")
131
+
132
+ if not data or data['org_count'] == 0:
133
+ self.logger.warning(f"No data found for seed {seed}")
134
+ return None
135
+
136
+ # Calculate coverage percentage
137
+ coverage = (data['org_count'] / len(self.model.db_files)) * 100
138
+ coverage = float("%.2f" % coverage)
139
+
140
+ # Calculate average repeats per scaffold
141
+ avg_rep_per_scaff = data['total_count'] / data['org_count']
142
+ avg_rep_per_scaff = float("%.2f" % avg_rep_per_scaff)
143
+
144
+ # Handle missing data in threes/fives
145
+ threes = data['threes']
146
+ fives = data['fives']
147
+ if len(threes) < len(fives):
148
+ threes.extend([''] * (len(fives) - len(threes)))
149
+ elif len(fives) < len(threes):
150
+ fives.extend([''] * (len(threes) - len(fives)))
151
+
152
+ # Find majority sequence
153
+ majority_index = 0
154
+ if not threes or threes[0] == '':
155
+ majority = max(set(fives), key=fives.count)
156
+ majority_index = fives.index(majority)
157
+ consensus_seq = fives[majority_index] + seed
158
+ percent_consensus = (fives.count(fives[majority_index]) / len(fives)) * 100
159
+ elif not fives or fives[0] == '':
160
+ majority = max(set(threes), key=threes.count)
161
+ majority_index = threes.index(majority)
162
+ consensus_seq = seed + threes[majority_index]
163
+ percent_consensus = (threes.count(threes[majority_index]) / len(threes)) * 100
164
+ else:
165
+ # Both threes and fives present
166
+ combined = [f"{f}{t}" for f, t in zip(fives, threes)]
167
+ majority = max(set(combined), key=combined.count)
168
+ majority_index = combined.index(majority)
169
+ consensus_seq = fives[majority_index] + seed + threes[majority_index]
170
+ percent_consensus = (combined.count(majority) / len(combined)) * 100
171
+
172
+ percent_consensus = float("%.2f" % percent_consensus)
173
+
174
+ # Determine strand
175
+ strand = "+" if int(data['locs'][majority_index]) >= 0 else "-"
176
+
177
+ # Create the row data
178
+ row_data = (
179
+ seed, # Seed
180
+ coverage, # % Coverage
181
+ data['total_count'], # Total Repeats
182
+ avg_rep_per_scaff, # Avg. Repeats/Scaffold
183
+ consensus_seq, # Consensus Sequence
184
+ percent_consensus, # % Consensus
185
+ data['scores'][majority_index], # Score
186
+ data['pams'][majority_index], # PAM
187
+ strand # Strand
188
+ )
189
+
190
+ self.logger.debug(f"Processed seed data: {row_data}")
191
+ return row_data
192
+
193
+ except Exception as e:
194
+ self.logger.error(f"Error processing seed data: {str(e)}")
195
+ show_error(self.global_settings, f"Error processing seed {seed}", str(e))
196
+ return None
197
 
198
  def custom_seed_search(self):
199
  try:
200
+ seeds = self.view.line_edit_seed.text().split(',')
201
  seeds = [seed.strip().upper() for seed in seeds if seed.strip()]
202
 
203
  if not seeds:
 
218
  )
219
  return
220
 
221
+ self.view.update_seed_table(seed_data)
222
  except Exception as e:
223
  show_error(self.global_settings, "Error in custom_seed_search() in population analysis.", str(e))
224
 
 
234
  )
235
  return
236
 
237
+ # Clear the locations table before adding new entries
238
+ self.view.table_locations.setRowCount(0)
239
+
240
+ # Get and display new locations
241
  locations = self.model.get_seed_locations(selected_seeds, self.model.db_files)
242
  self.view.update_loc_finder_table(locations)
243
  except Exception as e:
 
249
  except Exception as e:
250
  show_error(self.global_settings, "Error in clear_loc_table() in population analysis.", str(e))
251
 
252
+ def seed_table_sorting(self, logicalIndex):
253
  try:
254
  self.view.sort_table2(logicalIndex)
255
  except Exception as e:
256
+ show_error(self.global_settings, "Error in seed_table_sorting() in population analysis.", str(e))
257
 
258
  def loc_table_sorter(self, logicalIndex):
259
  try:
src/controllers/ViewTargetsController.py CHANGED
@@ -1,13 +1,21 @@
 
1
  from models.ViewTargetsModel import ViewTargetsModel
2
  from views.ViewTargetsView import ViewTargetsView
3
  from PyQt6.QtWidgets import QMessageBox
4
  from utils.ui import show_error
 
 
 
5
 
6
  class ViewTargetsController:
7
  def __init__(self, global_settings):
8
  self.global_settings = global_settings
 
9
  self.model = ViewTargetsModel(global_settings)
10
  self.view = ViewTargetsView(global_settings)
 
 
 
11
  self.setup_connections()
12
  self.organism = ""
13
  self.endonuclease = ""
@@ -26,28 +34,86 @@ class ViewTargetsController:
26
 
27
  def load_targets(self, selected_targets, organism, endonuclease):
28
  try:
 
 
29
  self.organism = organism
30
  self.endonuclease = endonuclease
 
 
 
31
  self.model.load_targets(selected_targets, organism, endonuclease)
 
 
 
 
 
32
  targets = self.model.get_targets()
 
 
 
 
 
33
  self.view.display_targets_in_table(targets)
 
 
 
 
 
34
  self.view.set_combo_box_endonuclease([endonuclease])
35
- self.load_gene_viewer(selected_targets)
 
 
 
 
 
 
 
 
 
 
 
36
  except Exception as e:
37
  show_error(self.global_settings, "Error loading targets", str(e))
38
 
39
- def load_gene_viewer(self, selected_targets):
40
  try:
41
- genes = self.model.available_genes
42
- self.view.set_combo_box_gene(genes)
 
 
43
  if genes:
44
- self.display_gene_data(genes[0])
45
- else:
46
- self.view.set_text_edit_gene_viewer("No gene data available")
47
- self.view.line_edit_start_location.clear()
48
- self.view.line_edit_stop_location.clear()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  except Exception as e:
50
- show_error(self.global_settings, "Error loading gene viewer", str(e))
51
 
52
  def perform_off_target_analysis(self):
53
  try:
@@ -77,13 +143,61 @@ class ViewTargetsController:
77
 
78
  def highlight_gene_viewer(self):
79
  try:
80
- selected_targets = self.view.get_selected_targets()
81
- if not selected_targets:
82
- QMessageBox.warning(self.view, "No Selection", "Please select targets to highlight in the gene viewer.")
 
 
 
 
 
 
83
  return
84
- highlighted_sequence = self.model.highlight_targets_in_gene_viewer(selected_targets)
85
- self.view.update_gene_viewer(highlighted_sequence)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  except Exception as e:
 
87
  show_error(self.global_settings, "Error highlighting gene viewer", str(e))
88
 
89
  def export_targets(self):
 
1
+ import logging
2
  from models.ViewTargetsModel import ViewTargetsModel
3
  from views.ViewTargetsView import ViewTargetsView
4
  from PyQt6.QtWidgets import QMessageBox
5
  from utils.ui import show_error
6
+ import time
7
+ import traceback
8
+ import threading
9
 
10
  class ViewTargetsController:
11
  def __init__(self, global_settings):
12
  self.global_settings = global_settings
13
+ start_time = time.time()
14
  self.model = ViewTargetsModel(global_settings)
15
  self.view = ViewTargetsView(global_settings)
16
+ init_time = time.time() - start_time
17
+ self.global_settings.logger.debug(f"ViewTargets initialization took: {init_time:.2f} seconds")
18
+
19
  self.setup_connections()
20
  self.organism = ""
21
  self.endonuclease = ""
 
34
 
35
  def load_targets(self, selected_targets, organism, endonuclease):
36
  try:
37
+ total_start = time.time()
38
+
39
  self.organism = organism
40
  self.endonuclease = endonuclease
41
+
42
+ # Time model loading
43
+ model_start = time.time()
44
  self.model.load_targets(selected_targets, organism, endonuclease)
45
+ model_time = time.time() - model_start
46
+ self.global_settings.logger.debug(f"Model load_targets took: {model_time:.2f} seconds")
47
+
48
+ # Time getting targets
49
+ targets_start = time.time()
50
  targets = self.model.get_targets()
51
+ targets_time = time.time() - targets_start
52
+ self.global_settings.logger.debug(f"Getting targets took: {targets_time:.2f} seconds")
53
+
54
+ # Time displaying targets
55
+ display_start = time.time()
56
  self.view.display_targets_in_table(targets)
57
+ display_time = time.time() - display_start
58
+ self.global_settings.logger.debug(f"Displaying targets took: {display_time:.2f} seconds")
59
+
60
+ # Time setting endonuclease
61
+ endo_start = time.time()
62
  self.view.set_combo_box_endonuclease([endonuclease])
63
+ endo_time = time.time() - endo_start
64
+ self.global_settings.logger.debug(f"Setting endonuclease took: {endo_time:.2f} seconds")
65
+
66
+ # Time loading gene viewer
67
+ gene_start = time.time()
68
+ self.load_gene_viewer()
69
+ gene_time = time.time() - gene_start
70
+ self.global_settings.logger.debug(f"Loading gene viewer took: {gene_time:.2f} seconds")
71
+
72
+ total_time = time.time() - total_start
73
+ self.global_settings.logger.debug(f"Total load_targets took: {total_time:.2f} seconds")
74
+
75
  except Exception as e:
76
  show_error(self.global_settings, "Error loading targets", str(e))
77
 
78
+ def load_gene_viewer(self):
79
  try:
80
+ start_time = time.time()
81
+
82
+ # Get available genes from the model
83
+ genes = self.model.get_available_genes()
84
  if genes:
85
+ # Update the gene combo box
86
+ self.view.combo_box_gene.clear()
87
+ self.view.combo_box_gene.addItems(genes)
88
+
89
+ # Fetch first gene immediately
90
+ first_gene = genes[0]
91
+ gene_data = self.model.get_gene_data(first_gene)
92
+
93
+ if gene_data:
94
+ # Update the gene viewer with sequence
95
+ self.view.set_text_edit_gene_viewer(gene_data['sequence'])
96
+
97
+ # Update location fields if available
98
+ if 'info' in gene_data and 'feature_location' in gene_data['info']:
99
+ location = gene_data['info']['feature_location']
100
+ if ':' in location:
101
+ start, end = location.split(':')[0], location.split(':')[1].split('(')[0]
102
+ self.view.line_edit_start_location.setText(start)
103
+ self.view.line_edit_stop_location.setText(end)
104
+
105
+ # Pre-fetch next few genes in background thread
106
+ def prefetch_genes():
107
+ for gene in genes[1:5]: # Pre-fetch next 4 genes
108
+ self.model.get_gene_data(gene)
109
+
110
+ threading.Thread(target=prefetch_genes, daemon=True).start()
111
+
112
+ execution_time = time.time() - start_time
113
+ self.global_settings.logger.debug(f"Loading gene viewer took: {execution_time:.2f} seconds")
114
+
115
  except Exception as e:
116
+ self.global_settings.logger.error(f"Error in load_gene_viewer: {str(e)}\n{traceback.format_exc()}")
117
 
118
  def perform_off_target_analysis(self):
119
  try:
 
143
 
144
  def highlight_gene_viewer(self):
145
  try:
146
+ self.global_settings.logger.debug("Starting highlight_gene_viewer")
147
+
148
+ # Get selected targets
149
+ selected_rows = self.view.get_selected_targets()
150
+ self.global_settings.logger.debug(f"Selected targets: {selected_rows}")
151
+
152
+ if not selected_rows:
153
+ QMessageBox.warning(self.view, "No Selection",
154
+ "Please select targets to highlight in the gene viewer.")
155
  return
156
+
157
+ # Convert table selections to the format expected by the model
158
+ targets_to_highlight = []
159
+ for target in selected_rows:
160
+ target_info = {
161
+ 'location': target['location'],
162
+ 'sequence': target['sequence'],
163
+ 'strand': target['strand']
164
+ }
165
+ targets_to_highlight.append(target_info)
166
+ self.global_settings.logger.debug(f"Target to highlight: {target_info}")
167
+
168
+ # Get current gene sequence
169
+ current_gene = self.view.combo_box_gene.currentText()
170
+ self.global_settings.logger.debug(f"Current gene: {current_gene}")
171
+
172
+ gene_data = self.model.get_gene_data(current_gene)
173
+ if not gene_data:
174
+ self.global_settings.logger.error("No gene data found")
175
+ QMessageBox.warning(self.view, "No Gene Data",
176
+ "Could not get gene data for highlighting.")
177
+ return
178
+
179
+ self.global_settings.logger.debug(f"Gene sequence length: {len(gene_data['sequence'])}")
180
+
181
+ # Highlight the sequences
182
+ if targets_to_highlight:
183
+ self.global_settings.logger.debug("Attempting to highlight sequences")
184
+ highlighted_sequence = self.model.highlight_targets_in_gene_viewer(targets_to_highlight)
185
+
186
+ if highlighted_sequence:
187
+ self.global_settings.logger.debug("Successfully highlighted sequences")
188
+ self.global_settings.logger.debug(f"Highlighted sequence length: {len(highlighted_sequence)}")
189
+ self.view.update_gene_viewer(highlighted_sequence)
190
+ else:
191
+ self.global_settings.logger.error("Failed to highlight sequences - returned None")
192
+ QMessageBox.warning(self.view, "Highlighting Failed",
193
+ "Could not highlight the selected sequences. They may not be found in the current gene view.")
194
+ else:
195
+ self.global_settings.logger.error("No valid targets to highlight")
196
+ QMessageBox.warning(self.view, "No Valid Targets",
197
+ "Could not get sequence information from the selected rows.")
198
+
199
  except Exception as e:
200
+ self.global_settings.logger.error(f"Error in highlight_gene_viewer: {str(e)}\n{traceback.format_exc()}")
201
  show_error(self.global_settings, "Error highlighting gene viewer", str(e))
202
 
203
  def export_targets(self):
src/models/AnnotationParser.py CHANGED
@@ -2,6 +2,7 @@ from PyQt6.QtWidgets import QMessageBox
2
  from Bio import SeqIO
3
  import os
4
  import traceback
 
5
 
6
  class AnnotationParser:
7
  def __init__(self, global_settings):
@@ -9,25 +10,34 @@ class AnnotationParser:
9
  self.logger = global_settings.get_logger()
10
  self.annotation_file_name = ""
11
  self.available_genes = []
 
 
 
12
 
13
  def set_annotation_file(self, file_path):
14
- self.annotation_file_name = file_path
15
- self.logger.debug(f"Set annotation file to: {file_path}")
16
- self._parse_available_genes()
17
-
18
- def get_max_chrom(self):
19
- try:
20
- parser = SeqIO.parse(self.annotation_file_name, 'genbank')
21
- max_chrom = sum(1 for _ in parser)
22
- return max_chrom
23
- except Exception as e:
24
- self.logger.error(f"Error in get_max_chrom: {str(e)}")
25
- self._show_error("Error in get_max_chrom", str(e))
26
- return 0
27
-
28
- def get_sequence_info(self, query):
29
- # Implement this method if needed
30
- pass
 
 
 
 
 
 
31
 
32
  def genbank_search(self, queries):
33
  try:
@@ -37,15 +47,34 @@ class AnnotationParser:
37
  self.logger.debug(f"Searching in annotation file: {self.annotation_file_name}")
38
  results_list = []
39
 
40
- for record in SeqIO.parse(self.annotation_file_name, "genbank"):
 
 
 
 
41
  for feature in record.features:
42
  if feature.type in ['CDS', 'gene']:
43
- feature_info = self._get_feature_info(feature)
44
- for query in queries:
45
- if query.lower() in feature_info['feature_name'].lower() or \
46
- query.lower() in feature_info['feature_id'].lower() or \
47
- query.lower() in feature_info['feature_description'].lower():
48
- results_list.append((record.id, feature))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  self.logger.debug(f"Found {len(results_list)} results")
51
  return results_list
@@ -53,6 +82,20 @@ class AnnotationParser:
53
  self.logger.error(f"Error in genbank_search: {str(e)}")
54
  raise
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  def find_which_file_version(self):
57
  try:
58
  if not self.annotation_file_name or os.path.basename(self.annotation_file_name) == "None":
@@ -110,25 +153,76 @@ class AnnotationParser:
110
  def get_available_genes(self):
111
  return self.available_genes
112
 
113
- def get_gene_data(self, gene_name):
 
 
 
114
  try:
115
- for record in SeqIO.parse(self.annotation_file_name, "genbank"):
116
- for feature in record.features:
117
- if feature.type == 'gene' and self._get_feature_name(feature) == gene_name:
118
- self.logger.debug(f"Found gene {gene_name} in annotation file")
119
- self.logger.debug(f"Its sequence is {feature.extract(record.seq)}")
120
- self.logger.debug(f"Its feature is {feature}")
121
- self.logger.debug(f"Its feature info is {self._get_feature_info(feature)}")
122
- return {
123
- 'sequence': str(feature.extract(record.seq)),
124
- 'info': self._get_feature_info(feature)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  }
126
- self.logger.warning(f"Gene {gene_name} not found in annotation file")
 
 
 
127
  return None
 
128
  except Exception as e:
129
  self.logger.error(f"Error in get_gene_data: {str(e)}")
130
  return None
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def _parse_available_genes(self):
133
  self.available_genes = []
134
  try:
 
2
  from Bio import SeqIO
3
  import os
4
  import traceback
5
+ from functools import lru_cache
6
 
7
  class AnnotationParser:
8
  def __init__(self, global_settings):
 
10
  self.logger = global_settings.get_logger()
11
  self.annotation_file_name = ""
12
  self.available_genes = []
13
+ self._feature_cache = {} # Cache for feature data
14
+ self._record_cache = {} # Cache for SeqIO records
15
+ self.gene_cache = {} # Add cache for gene data
16
 
17
  def set_annotation_file(self, file_path):
18
+ if self.annotation_file_name != file_path:
19
+ self.annotation_file_name = file_path
20
+ self.logger.debug(f"Set annotation file to: {file_path}")
21
+ self._feature_cache.clear() # Clear cache when file changes
22
+ self._record_cache.clear()
23
+ if hasattr(self, '_gene_index'):
24
+ delattr(self, '_gene_index')
25
+
26
+ # Pre-load records and build index
27
+ records = self._get_records()
28
+ self._build_gene_index(records)
29
+ self._parse_available_genes()
30
+
31
+ @lru_cache(maxsize=1)
32
+ def _get_records(self):
33
+ """Cache and return all records from the annotation file"""
34
+ if not self._record_cache:
35
+ try:
36
+ self._record_cache = list(SeqIO.parse(self.annotation_file_name, "genbank"))
37
+ except Exception as e:
38
+ self.logger.error(f"Error reading annotation file: {str(e)}")
39
+ return []
40
+ return self._record_cache
41
 
42
  def genbank_search(self, queries):
43
  try:
 
47
  self.logger.debug(f"Searching in annotation file: {self.annotation_file_name}")
48
  results_list = []
49
 
50
+ # Convert queries to lowercase set for faster lookup
51
+ queries = {q.lower() for q in queries}
52
+
53
+ # Use cached records
54
+ for record in self._get_records():
55
  for feature in record.features:
56
  if feature.type in ['CDS', 'gene']:
57
+ # Create a hashable cache key using feature start and end positions
58
+ cache_key = (record.id, feature.type,
59
+ str(feature.location.start),
60
+ str(feature.location.end))
61
+
62
+ # Use cached feature info if available
63
+ if cache_key not in self._feature_cache:
64
+ self._feature_cache[cache_key] = self._get_feature_info(feature)
65
+
66
+ feature_info = self._feature_cache[cache_key]
67
+
68
+ # Combine searchable text for single comparison
69
+ searchable_text = ' '.join([
70
+ feature_info['feature_name'].lower(),
71
+ feature_info['feature_id'].lower(),
72
+ feature_info['feature_description'].lower()
73
+ ])
74
+
75
+ # Check if any query matches
76
+ if any(query in searchable_text for query in queries):
77
+ results_list.append((record.id, feature))
78
 
79
  self.logger.debug(f"Found {len(results_list)} results")
80
  return results_list
 
82
  self.logger.error(f"Error in genbank_search: {str(e)}")
83
  raise
84
 
85
+ def get_max_chrom(self):
86
+ try:
87
+ parser = SeqIO.parse(self.annotation_file_name, 'genbank')
88
+ max_chrom = sum(1 for _ in parser)
89
+ return max_chrom
90
+ except Exception as e:
91
+ self.logger.error(f"Error in get_max_chrom: {str(e)}")
92
+ self._show_error("Error in get_max_chrom", str(e))
93
+ return 0
94
+
95
+ def get_sequence_info(self, query):
96
+ # Implement this method if needed
97
+ pass
98
+
99
  def find_which_file_version(self):
100
  try:
101
  if not self.annotation_file_name or os.path.basename(self.annotation_file_name) == "None":
 
153
  def get_available_genes(self):
154
  return self.available_genes
155
 
156
+ def get_gene_data(self, gene_identifier):
157
+ """
158
+ Get gene data using gene name or locus tag with optimized caching
159
+ """
160
  try:
161
+ self.logger.debug(f"AnnotationParser.get_gene_data called with identifier: {gene_identifier}")
162
+
163
+ if not gene_identifier:
164
+ self.logger.warning("Empty gene identifier provided")
165
+ return None
166
+
167
+ # Handle numeric gene identifiers
168
+ if isinstance(gene_identifier, int) or str(gene_identifier).isdigit():
169
+ if self.available_genes:
170
+ gene_identifier = self.available_genes[0]
171
+ else:
172
+ return None
173
+
174
+ # Check main cache first
175
+ cache_key = f"gene_data_{gene_identifier}"
176
+ if cache_key in self._feature_cache:
177
+ return self._feature_cache[cache_key]
178
+
179
+ # Get cached records
180
+ records = self._get_records()
181
+ if not records:
182
+ return None
183
+
184
+ # Use gene index if available
185
+ if not hasattr(self, '_gene_index'):
186
+ self._build_gene_index(records)
187
+
188
+ # Try to get location from index
189
+ if gene_identifier in self._gene_index:
190
+ record_id, feature = self._gene_index[gene_identifier]
191
+ for record in records:
192
+ if record.id == record_id:
193
+ sequence = str(feature.extract(record.seq))
194
+ feature_info = self._get_feature_info(feature)
195
+
196
+ result = {
197
+ 'sequence': sequence,
198
+ 'info': feature_info
199
  }
200
+
201
+ self._feature_cache[cache_key] = result
202
+ return result
203
+
204
  return None
205
+
206
  except Exception as e:
207
  self.logger.error(f"Error in get_gene_data: {str(e)}")
208
  return None
209
 
210
+ def _build_gene_index(self, records):
211
+ """Build an index of genes for faster lookup"""
212
+ self._gene_index = {}
213
+ try:
214
+ for record in records:
215
+ for feature in record.features:
216
+ if feature.type == 'gene':
217
+ gene_name = self._get_feature_name(feature)
218
+ gene_id = self._get_feature_id(feature)
219
+ if gene_name != "N/A":
220
+ self._gene_index[gene_name] = (record.id, feature)
221
+ if gene_id != "N/A":
222
+ self._gene_index[gene_id] = (record.id, feature)
223
+ except Exception as e:
224
+ self.logger.error(f"Error building gene index: {str(e)}")
225
+
226
  def _parse_available_genes(self):
227
  self.available_genes = []
228
  try:
src/models/CSPRparser.py CHANGED
@@ -1,27 +1,129 @@
1
  from utils.sequence_utils import SeqTranslate
2
- import gzip
3
-
4
- ##################################################################################################################################
5
- # Use: Use as a parser for the cspr files
6
- # Precondition: Only to the used with .cspr files. Will not work with any other files
7
- # This class also took some of the parsing functions from with classes (Multitargeting and Results) and stores them in here
8
- ##################################################################################################################################
9
 
10
  class CSPRparser:
11
  def __init__(self, inputFileName, casper_info_path):
12
  self.fileName = inputFileName
 
13
  self.seqTrans = SeqTranslate(casper_info_path)
 
 
 
14
 
15
- def read_targets(self, genename, pos_tuple, endo):
16
- retList = []
17
- with open(self.fileName, 'r') as f:
18
- for line in f:
19
- if line.startswith(f">{pos_tuple[0]}"):
20
- for line in f:
21
- if line.startswith('>'):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  break
23
- line = line.strip().split(',')
24
- if pos_tuple[1] <= abs(int(line[0])) < pos_tuple[2]:
25
- strand = "-" if int(line[0]) < 0 else "+"
26
- retList.append((line[0], line[1], line[2], line[3], strand, endo))
27
- return retList
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from utils.sequence_utils import SeqTranslate
2
+ import logging
3
+ from multiprocessing import Pool, cpu_count
4
+ from functools import partial
 
 
 
 
5
 
6
  class CSPRparser:
7
  def __init__(self, inputFileName, casper_info_path):
8
  self.fileName = inputFileName
9
+ self.filename = inputFileName
10
  self.seqTrans = SeqTranslate(casper_info_path)
11
+ self.logger = logging.getLogger(__name__)
12
+ self._line_buffer = [] # Pre-allocate buffer for lines
13
+ self._cached_results = {}
14
 
15
+ def read_targets_batch(self, chromosome, targets, endonuclease):
16
+ """Ultra-fast target reading using direct tuple creation"""
17
+ try:
18
+ # Pre-process targets into a sorted list of ranges for faster lookup
19
+ target_ranges = []
20
+ for t in targets:
21
+ start = t['start']
22
+ end = t['end']
23
+ target_ranges.append((start, end, t['feature_name']))
24
+ target_ranges.sort() # Sort by start position
25
+
26
+ # Pre-allocate results list
27
+ results = []
28
+ results_append = results.append
29
+
30
+ # Read file in binary mode for speed
31
+ with open(self.fileName, 'rb') as f:
32
+ # Skip header
33
+ for _ in range(3):
34
+ f.readline()
35
+
36
+ # Find chromosome section
37
+ header = False
38
+ for line in f:
39
+ if b'>' in line and str(chromosome).encode() in line:
40
+ header = True
41
+ break
42
+
43
+ # Read targets
44
+ if header:
45
+ current_range_idx = 0
46
+ max_ranges = len(target_ranges)
47
+
48
+ while current_range_idx < max_ranges:
49
+ line = f.readline()
50
+ if not line or line.startswith(b'>'):
51
  break
52
+
53
+ if not line.strip():
54
+ continue
55
+
56
+ # Fast string splitting without decode
57
+ parts = line.strip().split(b',')
58
+ if not parts:
59
+ continue
60
+
61
+ try:
62
+ pos = int(parts[0])
63
+ abs_pos = abs(pos)
64
+
65
+ # Get current target range
66
+ start, end, feature_name = target_ranges[current_range_idx]
67
+
68
+ # Skip if position is past current range
69
+ if abs_pos >= end:
70
+ current_range_idx += 1
71
+ continue
72
+
73
+ # Check if position is in range
74
+ if start <= abs_pos < end:
75
+ sequence = parts[1].decode()
76
+ pam = sequence[-3:]
77
+ target_seq = sequence[:-3]
78
+
79
+ results_append({
80
+ 'feature_name': feature_name,
81
+ 'chromosome': chromosome,
82
+ 'position': abs_pos,
83
+ 'location': f"{abs_pos}-{abs_pos + 23}",
84
+ 'sequence': target_seq,
85
+ 'pam': pam,
86
+ 'strand': "-" if pos < 0 else "+",
87
+ 'score': float(parts[3]) if len(parts) > 3 else 0.0,
88
+ 'endonuclease': endonuclease
89
+ })
90
+
91
+ except (ValueError, IndexError):
92
+ continue
93
+
94
+ return results
95
+
96
+ except Exception as e:
97
+ self.logger.error(f"Error in read_targets_batch: {str(e)}")
98
+ return []
99
+
100
+ def parse_targets(self, file_path, region):
101
+ """Parse targets with parallel processing and caching"""
102
+ cache_key = f"{file_path}:{region}"
103
+ if cache_key in self._cached_results:
104
+ return self._cached_results[cache_key]
105
+
106
+ # Split the region into chunks for parallel processing
107
+ chunks = self._split_region(region)
108
+
109
+ with Pool() as pool:
110
+ results = pool.map(partial(self._parse_chunk, file_path), chunks)
111
+
112
+ # Combine results
113
+ combined_targets = []
114
+ for chunk_result in results:
115
+ combined_targets.extend(chunk_result)
116
+
117
+ self._cached_results[cache_key] = combined_targets
118
+ return combined_targets
119
+
120
+ def _split_region(self, region):
121
+ """Split a region into chunks for parallel processing"""
122
+ start, end = region
123
+ chunk_size = (end - start) // cpu_count()
124
+ chunks = []
125
+ for i in range(cpu_count()):
126
+ chunk_start = start + (i * chunk_size)
127
+ chunk_end = chunk_start + chunk_size if i < cpu_count()-1 else end
128
+ chunks.append((chunk_start, chunk_end))
129
+ return chunks
src/models/DatabaseManager.py CHANGED
@@ -1,5 +1,8 @@
1
  import os
2
  from PyQt6.QtCore import QObject, pyqtSignal, QFileSystemWatcher
 
 
 
3
 
4
  class DatabaseManager(QObject):
5
  db_state_updated = pyqtSignal(bool, str, list) # Combined signal
@@ -115,9 +118,19 @@ class DatabaseManager(QObject):
115
  def _on_directory_changed(self, path):
116
  """Handle changes in the watched directory."""
117
  self.logger.debug(f"Detected change in directory: {path}")
118
- cspr_files = self._get_cspr_files()
 
119
  is_valid, message = self.validate_db_path(path)
 
 
 
 
 
 
120
  self.db_state_updated.emit(is_valid, message, cspr_files)
 
 
 
121
 
122
  def _get_cspr_files(self):
123
  """Get a list of CSPR files in the current database directory."""
@@ -125,6 +138,15 @@ class DatabaseManager(QObject):
125
  return []
126
  return [f for f in os.listdir(self.db_path) if f.endswith('.cspr')]
127
 
 
 
 
 
 
 
 
 
 
128
  def check_db_state(self):
129
  """Check the current state of the database and emit signals if changed."""
130
  self.logger.debug("Checking database state")
@@ -135,8 +157,273 @@ class DatabaseManager(QObject):
135
  self.logger.debug(f"Database state: valid={is_valid}, message={message}")
136
 
137
  cspr_files = self._get_cspr_files()
138
- self.logger.debug(f"Found {len(cspr_files)} CSPR files in the database")
139
 
140
- message = f"Database is valid. Contains {len(cspr_files)} CSPR files."
141
 
142
  self.db_state_updated.emit(is_valid, message, cspr_files)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  from PyQt6.QtCore import QObject, pyqtSignal, QFileSystemWatcher
3
+ import sqlite3
4
+ from collections import Counter
5
+ import statistics
6
 
7
  class DatabaseManager(QObject):
8
  db_state_updated = pyqtSignal(bool, str, list) # Combined signal
 
118
  def _on_directory_changed(self, path):
119
  """Handle changes in the watched directory."""
120
  self.logger.debug(f"Detected change in directory: {path}")
121
+
122
+ # Get current state
123
  is_valid, message = self.validate_db_path(path)
124
+
125
+ # Get list of files
126
+ cspr_files = self._get_cspr_files()
127
+ gbff_files = self._get_gbff_files() # Add method to get GBFF files
128
+
129
+ # Emit the signal with updated state
130
  self.db_state_updated.emit(is_valid, message, cspr_files)
131
+
132
+ # Log the change
133
+ self.logger.info(f"Database state updated - Valid: {is_valid}, Files: {len(cspr_files)} CSPR, {len(gbff_files)} GBFF")
134
 
135
  def _get_cspr_files(self):
136
  """Get a list of CSPR files in the current database directory."""
 
138
  return []
139
  return [f for f in os.listdir(self.db_path) if f.endswith('.cspr')]
140
 
141
+ def _get_gbff_files(self):
142
+ """Get a list of GBFF files in the database directory."""
143
+ if not self.db_path or not os.path.isdir(self.db_path):
144
+ return []
145
+ gbff_path = os.path.join(self.db_path, 'GBFF')
146
+ if not os.path.exists(gbff_path):
147
+ return []
148
+ return [f for f in os.listdir(gbff_path) if f.endswith('.gbff')]
149
+
150
  def check_db_state(self):
151
  """Check the current state of the database and emit signals if changed."""
152
  self.logger.debug("Checking database state")
 
157
  self.logger.debug(f"Database state: valid={is_valid}, message={message}")
158
 
159
  cspr_files = self._get_cspr_files()
160
+ gbff_files = self._get_gbff_files()
161
 
162
+ message = f"Database is valid. Contains {len(cspr_files)} CSPR files and {len(gbff_files)} GBFF files."
163
 
164
  self.db_state_updated.emit(is_valid, message, cspr_files)
165
+ self.logger.info(f"Database state checked - Valid: {is_valid}, Files: {len(cspr_files)} CSPR, {len(gbff_files)} GBFF")
166
+
167
+ def get_organisms_and_endos(self):
168
+ """Get mapping of organisms to their endonucleases and files"""
169
+ try:
170
+ if not self.db_path or not os.path.exists(self.db_path):
171
+ self.logger.error(f"Invalid database path: {self.db_path}")
172
+ return {}, {}
173
+
174
+ onlyfiles = [f for f in os.listdir(self.db_path) if os.path.isfile(os.path.join(self.db_path, f))]
175
+ cspr_files = [f for f in onlyfiles if f.endswith('.cspr')]
176
+
177
+ organisms_to_files = {}
178
+ organisms_to_endos = {}
179
+
180
+ for file in cspr_files:
181
+ try:
182
+ # Parse filename
183
+ newname = file[0:-5] # Remove .cspr - changed from -4 to -5
184
+ endo = newname[newname.rfind("_") + 1:] # Get endonuclease name
185
+
186
+ # Read organism name from first line of CSPR file
187
+ file_path = os.path.join(self.db_path, file)
188
+ with open(file_path, 'r') as hold:
189
+ buf = hold.readline().strip()
190
+ species = buf.replace("GENOME: ", "")
191
+
192
+ # Store file mappings
193
+ if species in organisms_to_files:
194
+ organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
195
+ else:
196
+ organisms_to_files[species] = {}
197
+ organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
198
+
199
+ # Store endonuclease mappings
200
+ if species in organisms_to_endos:
201
+ if endo not in organisms_to_endos[species]:
202
+ organisms_to_endos[species].append(endo)
203
+ else:
204
+ organisms_to_endos[species] = [endo]
205
+
206
+ except Exception as e:
207
+ self.logger.error(f"Error processing file {file}: {str(e)}")
208
+ continue
209
+
210
+ return organisms_to_files, organisms_to_endos
211
+
212
+ except Exception as e:
213
+ self.logger.error(f"Error getting organisms and endonucleases: {str(e)}")
214
+ return {}, {}
215
+
216
+ def get_repeats_data(self, db_file, row_limit=-1):
217
+ """Get repeats data for the seeds table"""
218
+ try:
219
+ conn = sqlite3.connect(db_file)
220
+ c = conn.cursor()
221
+
222
+ if row_limit == -1:
223
+ sql_query = "SELECT * FROM repeats ORDER BY count DESC;"
224
+ else:
225
+ sql_query = f"SELECT * FROM repeats ORDER BY count DESC LIMIT 0, {row_limit};"
226
+
227
+ repeats = c.execute(sql_query).fetchall()
228
+ processed_data = []
229
+
230
+ for repeat in repeats:
231
+ # Extract repeat info
232
+ seed = repeat[0]
233
+ chroms = repeat[1].split(",")
234
+ locs = repeat[2].split(",")
235
+ threes = repeat[3].split(",")
236
+ fives = repeat[4].split(",")
237
+ pams = repeat[5].split(",")
238
+ scores = repeat[6].split(",")
239
+ count = repeat[7]
240
+
241
+ # Handle missing data in threes/fives
242
+ if len(threes) < len(fives):
243
+ threes.extend([''] * (len(fives) - len(threes)))
244
+ elif len(fives) < len(threes):
245
+ fives.extend([''] * (len(threes) - len(fives)))
246
+
247
+ # Find majority sequence
248
+ majority_index = 0
249
+ three_prime, five_prime, both_prime = False, False, False
250
+ if threes[0] == '':
251
+ majority = max(set(fives), key=fives.count)
252
+ majority_index = fives.index(majority)
253
+ five_prime = True
254
+ elif fives[0] == '':
255
+ majority = max(set(threes), key=threes.count)
256
+ majority_index = threes.index(majority)
257
+ three_prime = True
258
+ else:
259
+ # account for both 3 and 5 present
260
+ threes_and_fives = []
261
+ for i in range(len(threes)):
262
+ threes_and_fives.append(threes[i] + fives[i])
263
+ majority = max(set(threes_and_fives), key=threes_and_fives.count)
264
+ majority_index = threes_and_fives.index(majority)
265
+ both_prime = True
266
+
267
+ # Calculate average repeats per scaffold
268
+ location_repeat_counts = Counter(chroms)
269
+ avg_rep_per_scaff = sum(location_repeat_counts.values()) / len(location_repeat_counts.values())
270
+ avg_rep_per_scaff = float("%.2f" % avg_rep_per_scaff)
271
+
272
+ # Calculate consensus percentage
273
+ if five_prime:
274
+ percent_consensus = (fives.count(fives[majority_index]) / len(fives)) * 100
275
+ elif three_prime:
276
+ percent_consensus = (threes.count(threes[majority_index]) / len(threes)) * 100
277
+ elif both_prime:
278
+ percent_consensus = (threes_and_fives.count(threes_and_fives[majority_index]) / len(threes_and_fives)) * 100
279
+ percent_consensus = float("%.2f" % percent_consensus)
280
+
281
+ # Determine strand
282
+ strand = "+" if int(locs[majority_index]) >= 0 else "-"
283
+
284
+ # Create processed row
285
+ processed_row = (
286
+ seed, # Seed
287
+ count, # Total Repeats
288
+ avg_rep_per_scaff, # Avg. Repeats/Scaffold
289
+ fives[majority_index] + seed + threes[majority_index], # Consensus Sequence
290
+ percent_consensus, # % Consensus
291
+ scores[majority_index], # Score
292
+ pams[majority_index], # PAM
293
+ strand # Strand
294
+ )
295
+ processed_data.append(processed_row)
296
+
297
+ conn.close()
298
+ return processed_data
299
+
300
+ except Exception as e:
301
+ self.logger.error(f"Error getting repeats data: {str(e)}")
302
+ raise
303
+
304
+ def get_seed_data(self, db_file, seed):
305
+ """Get detailed data for a specific seed"""
306
+ try:
307
+ conn = sqlite3.connect(db_file)
308
+ c = conn.cursor()
309
+ data = c.execute("""
310
+ SELECT chromosome, location, pam, score,
311
+ five, three
312
+ FROM repeats
313
+ WHERE seed = ?
314
+ """, (seed,)).fetchall()
315
+ conn.close()
316
+ return data
317
+ except Exception as e:
318
+ self.logger.error(f"Error getting seed data: {str(e)}")
319
+ raise
320
+
321
+ def get_chro_bar_data(self, db_file, seed):
322
+ """Get chromosome distribution data for a seed"""
323
+ try:
324
+ conn = sqlite3.connect(db_file)
325
+ c = conn.cursor()
326
+ data = c.execute("SELECT chromosome FROM repeats WHERE seed = ?", (seed,)).fetchone()
327
+ conn.close()
328
+
329
+ if not data:
330
+ return Counter()
331
+
332
+ # Split the chromosome string and convert to integers
333
+ chromosomes = [int(x) for x in data[0].split(',')]
334
+ counts = Counter(chromosomes)
335
+ return counts
336
+
337
+ except Exception as e:
338
+ self.logger.error(f"Error getting chromosome bar data: {str(e)}")
339
+ raise
340
+
341
+ def get_seeds_vs_repeats_data(self, db_file):
342
+ """Get data for seeds vs repeats plot"""
343
+ try:
344
+ conn = sqlite3.connect(db_file)
345
+ c = conn.cursor()
346
+
347
+ # Count how many sequences have each repeat count
348
+ data = c.execute("""
349
+ SELECT count, COUNT(*) as num_sequences
350
+ FROM repeats
351
+ GROUP BY count
352
+ ORDER BY count ASC
353
+ """).fetchall()
354
+
355
+ conn.close()
356
+
357
+ if not data:
358
+ return None
359
+
360
+ # Separate into x and y values
361
+ x_vals = [row[0] for row in data] # number of repeats
362
+ y_vals = [row[1] for row in data] # number of sequences with that count
363
+
364
+ return {
365
+ 'x_vals': x_vals,
366
+ 'y_vals': y_vals
367
+ }
368
+
369
+ except Exception as e:
370
+ self.logger.error(f"Error getting seeds vs repeats data: {str(e)}")
371
+ raise
372
+
373
+ def get_repeats_vs_seeds_data(self, db_file):
374
+ """Get data for repeats vs seeds plot"""
375
+ try:
376
+ self.logger.debug(f"Getting repeats vs seeds data from {db_file}")
377
+
378
+ conn = sqlite3.connect(db_file)
379
+ c = conn.cursor()
380
+
381
+ # Get all count values ordered by rowid to maintain order
382
+ self.logger.debug("Executing SQL query")
383
+ data = c.execute("SELECT count FROM repeats ORDER BY rowid;").fetchall()
384
+ self.logger.debug(f"Raw data from database: {data[:5]}...") # Log first 5 entries
385
+
386
+ conn.close()
387
+
388
+ # Extract counts from tuples
389
+ counts = [row[0] for row in data]
390
+ self.logger.debug(f"Processed counts (first 5): {counts[:5]}...")
391
+
392
+ if not counts:
393
+ self.logger.warning("No count data found")
394
+ return None
395
+
396
+ # Calculate statistics
397
+ stats = {
398
+ 'average': statistics.mean(counts),
399
+ 'median': statistics.median(counts),
400
+ 'mode': statistics.mode(counts),
401
+ 'repeat_count': len(counts)
402
+ }
403
+ self.logger.debug(f"Calculated statistics: {stats}")
404
+
405
+ result = {
406
+ 'counts': counts,
407
+ 'stats': stats
408
+ }
409
+ self.logger.debug("Successfully prepared repeats vs seeds data")
410
+ return result
411
+
412
+ except Exception as e:
413
+ self.logger.error(f"Error getting repeats vs seeds data: {str(e)}")
414
+ raise
415
+
416
+ def calculate_statistics(self, db_file):
417
+ """Calculate global statistics"""
418
+ try:
419
+ conn = sqlite3.connect(db_file)
420
+ c = conn.cursor()
421
+
422
+ stats = {}
423
+ # Add your statistics calculations here
424
+
425
+ conn.close()
426
+ return stats
427
+ except Exception as e:
428
+ self.logger.error(f"Error calculating statistics: {str(e)}")
429
+ raise
src/models/FindTargetsModel.py CHANGED
@@ -2,72 +2,106 @@ from models.HomeWindowModel import HomeWindowModel
2
  from models.CSPRparser import CSPRparser
3
  from models.AnnotationParser import AnnotationParser
4
  import os
 
5
 
6
  class FindTargetsModel(HomeWindowModel):
7
  def __init__(self, global_settings):
8
  super().__init__(global_settings)
9
  self.results = {}
10
- self.annotation_parser = AnnotationParser(global_settings)
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  def find_targets(self, input_data):
13
  self.global_settings.logger.debug(f"Received input data: {input_data}")
14
- print(f"Received input data: {input_data}")
15
 
16
  organism = input_data['organism']
17
  endo = input_data['endonuclease']
18
-
19
  org_files = self.get_organism_to_files()
20
 
21
- if organism not in org_files:
22
- error_msg = f"Organism '{organism}' not found in the database. Available organisms: {list(org_files.keys())}"
23
- self.global_settings.logger.error(error_msg)
24
- raise ValueError(error_msg)
25
-
26
- if endo not in org_files[organism]:
27
- error_msg = f"Endonuclease '{endo}' not found for organism '{organism}'. Available endonucleases: {list(org_files[organism].keys())}"
28
- self.global_settings.logger.error(error_msg)
29
- raise ValueError(error_msg)
30
 
 
31
  file_path = os.path.join(self.global_settings.get_db_path(), org_files[organism][endo][0])
 
32
 
33
- parser = CSPRparser(file_path, self.global_settings.get_casper_info_path())
 
 
 
 
 
34
 
35
- if input_data['search_type'] == 'feature':
36
- self.results = self.find_targets_by_feature(parser, input_data)
37
- elif input_data['search_type'] == 'position':
38
- self.results = self.find_targets_by_position(parser, input_data)
39
- elif input_data['search_type'] == 'sequence':
40
- self.results = self.find_targets_by_sequence(parser, input_data)
41
- else:
42
  error_msg = f"Invalid search type: {input_data['search_type']}"
43
  self.global_settings.logger.error(error_msg)
44
  raise ValueError(error_msg)
45
 
 
46
  return self.results
47
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  def find_targets_by_feature(self, parser, input_data):
49
- annotation_file = input_data['annotation_file']
 
 
 
50
  search_query = input_data['search_query'].strip().lower()
51
 
 
 
52
  annotation_file_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
53
- self.annotation_parser.set_annotation_file(annotation_file_path)
54
 
55
  try:
56
- results_list = self.annotation_parser.genbank_search([search_query])
57
  self.global_settings.logger.debug(f"Genbank search results: {results_list}")
58
  except Exception as e:
59
  self.global_settings.logger.error(f"Error in genbank_search: {str(e)}")
60
  return []
61
 
 
 
62
  formatted_results = []
 
 
63
  for chrom, feature in results_list:
64
  if feature.type in ['CDS']:
65
  feature_info = self._get_feature_info(feature)
66
- if (search_query == feature_info['feature_name'].lower() or
67
- search_query == feature_info['feature_id'].lower() or
68
- search_query in feature_info['feature_name'].lower() or
69
- search_query in feature_info['feature_id'].lower() or
70
- search_query in feature_info['feature_description'].lower()):
 
 
 
 
 
71
  formatted_results.append({
72
  'feature_type': feature.type,
73
  'chromosome': chrom,
 
2
  from models.CSPRparser import CSPRparser
3
  from models.AnnotationParser import AnnotationParser
4
  import os
5
+ from functools import lru_cache
6
 
7
  class FindTargetsModel(HomeWindowModel):
8
  def __init__(self, global_settings):
9
  super().__init__(global_settings)
10
  self.results = {}
11
+ self._parser_cache = {} # Cache for CSPRparser instances
12
+ self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
13
+
14
+ def _on_annotation_file_changed(self, new_annotation_file):
15
+ """Clear caches when annotation file changes"""
16
+ self.global_settings.logger.debug(f"FindTargetsModel clearing caches for new annotation file: {new_annotation_file}")
17
+ self._parser_cache.clear()
18
+
19
+ @lru_cache(maxsize=32)
20
+ def _get_parser(self, file_path):
21
+ """Cache CSPRparser instances for reuse"""
22
+ if file_path not in self._parser_cache:
23
+ self._parser_cache[file_path] = CSPRparser(file_path, self.global_settings.get_casper_info_path())
24
+ return self._parser_cache[file_path]
25
 
26
  def find_targets(self, input_data):
27
  self.global_settings.logger.debug(f"Received input data: {input_data}")
 
28
 
29
  organism = input_data['organism']
30
  endo = input_data['endonuclease']
 
31
  org_files = self.get_organism_to_files()
32
 
33
+ # Validate input data
34
+ self._validate_input(organism, endo, org_files)
 
 
 
 
 
 
 
35
 
36
+ # Get file path and parser
37
  file_path = os.path.join(self.global_settings.get_db_path(), org_files[organism][endo][0])
38
+ parser = self._get_parser(file_path)
39
 
40
+ # Use dictionary for faster lookup
41
+ search_types = {
42
+ 'feature': self.find_targets_by_feature,
43
+ 'position': self.find_targets_by_position,
44
+ 'sequence': self.find_targets_by_sequence
45
+ }
46
 
47
+ search_func = search_types.get(input_data['search_type'])
48
+ if not search_func:
 
 
 
 
 
49
  error_msg = f"Invalid search type: {input_data['search_type']}"
50
  self.global_settings.logger.error(error_msg)
51
  raise ValueError(error_msg)
52
 
53
+ self.results = search_func(parser, input_data)
54
  return self.results
55
 
56
+ def _validate_input(self, organism, endo, org_files):
57
+ """Validate input parameters"""
58
+ if organism not in org_files:
59
+ error_msg = f"Organism '{organism}' not found in the database. Available organisms: {list(org_files.keys())}"
60
+ self.global_settings.logger.error(error_msg)
61
+ raise ValueError(error_msg)
62
+
63
+ if endo not in org_files[organism]:
64
+ error_msg = f"Endonuclease '{endo}' not found for organism '{organism}'. Available endonucleases: {list(org_files[organism].keys())}"
65
+ self.global_settings.logger.error(error_msg)
66
+ raise ValueError(error_msg)
67
+
68
  def find_targets_by_feature(self, parser, input_data):
69
+ # Get annotation file from input data or global settings
70
+ annotation_file = (input_data.get('annotation_file') or
71
+ self.global_settings.get_current_annotation_file())
72
+
73
  search_query = input_data['search_query'].strip().lower()
74
 
75
+ # Create new annotation parser instance for each search
76
+ annotation_parser = AnnotationParser(self.global_settings)
77
  annotation_file_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
78
+ annotation_parser.set_annotation_file(annotation_file_path)
79
 
80
  try:
81
+ results_list = annotation_parser.genbank_search([search_query])
82
  self.global_settings.logger.debug(f"Genbank search results: {results_list}")
83
  except Exception as e:
84
  self.global_settings.logger.error(f"Error in genbank_search: {str(e)}")
85
  return []
86
 
87
+ # Use a set for faster lookups
88
+ search_terms = {search_query}
89
  formatted_results = []
90
+
91
+ # Pre-calculate feature info once for each feature
92
  for chrom, feature in results_list:
93
  if feature.type in ['CDS']:
94
  feature_info = self._get_feature_info(feature)
95
+
96
+ # Combine all searchable text into one string for a single search operation
97
+ searchable_text = ' '.join([
98
+ feature_info['feature_name'].lower(),
99
+ feature_info['feature_id'].lower(),
100
+ feature_info['feature_description'].lower()
101
+ ])
102
+
103
+ # Single check if any search term is in the searchable text
104
+ if any(term in searchable_text for term in search_terms):
105
  formatted_results.append({
106
  'feature_type': feature.type,
107
  'chromosome': chrom,
src/models/GlobalSettings.py CHANGED
@@ -15,6 +15,7 @@ class GlobalSettings(QObject):
15
  db_state_updated = pyqtSignal(bool, str, list) # Combined signal
16
  first_time_startup = pyqtSignal() # New signal
17
  endonuclease_updated = pyqtSignal()
 
18
 
19
  def __init__(self, app_dir_path):
20
  super().__init__()
@@ -45,6 +46,8 @@ class GlobalSettings(QObject):
45
 
46
  self.main_window = None
47
 
 
 
48
  def _initialize_directories(self):
49
  self.src_dir_path = os.path.join(self.app_dir_path, 'src')
50
  self.ui_dir_path = os.path.join(self.src_dir_path, self.config_manager.get_config_value('paths.ui'))
@@ -268,5 +271,19 @@ class GlobalSettings(QObject):
268
  def get_endonucleases(self):
269
  return self.config_manager.get_endonucleases()
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  # Global instance
272
  global_settings = None
 
15
  db_state_updated = pyqtSignal(bool, str, list) # Combined signal
16
  first_time_startup = pyqtSignal() # New signal
17
  endonuclease_updated = pyqtSignal()
18
+ annotation_file_changed = pyqtSignal(str) # New signal for annotation file changes
19
 
20
  def __init__(self, app_dir_path):
21
  super().__init__()
 
46
 
47
  self.main_window = None
48
 
49
+ self._current_annotation_file = None
50
+
51
  def _initialize_directories(self):
52
  self.src_dir_path = os.path.join(self.app_dir_path, 'src')
53
  self.ui_dir_path = os.path.join(self.src_dir_path, self.config_manager.get_config_value('paths.ui'))
 
271
  def get_endonucleases(self):
272
  return self.config_manager.get_endonucleases()
273
 
274
+ def set_current_annotation_file(self, annotation_file):
275
+ """Set the current annotation file and notify listeners"""
276
+ if self._current_annotation_file != annotation_file:
277
+ self._current_annotation_file = annotation_file
278
+ self.logger.debug(f"Current annotation file changed to: {annotation_file}")
279
+ self.annotation_file_changed.emit(annotation_file)
280
+
281
+ def get_current_annotation_file(self):
282
+ """Get the currently selected annotation file"""
283
+ if not self._current_annotation_file and hasattr(self, '_current_home_window'):
284
+ # Try to get from home window if not set
285
+ self._current_annotation_file = self._current_home_window.get_annotation_file()
286
+ return self._current_annotation_file
287
+
288
  # Global instance
289
  global_settings = None
src/models/MultitargetingWindowModel.py CHANGED
@@ -1,140 +1,132 @@
1
  import os
2
- import sqlite3
3
- from collections import Counter
4
- import statistics
5
- import models.GlobalSettings as GlobalSettings
6
- from models.CSPRparser import CSPRparser
7
 
8
  class MultitargetingWindowModel:
9
  def __init__(self, global_settings):
10
- self.global_settings = global_settings
 
 
11
  self.cspr_file = ""
12
  self.db_file = ""
13
- self.organisms_to_files = {}
14
- self.organisms_to_endos = {}
15
- self.chromo_length = []
16
- self.max_repeats = 1
17
- self.average = 0
18
- self.median = 0
19
- self.mode = 0
20
- self.average_unique = 0
21
- self.average_rep = 0
22
- self.repeat_count = 0
23
  self.row_limit = 1000
24
- self.parser = CSPRparser("", self.global_settings.get_casper_info_path())
25
- self.load_organisms_and_endos()
26
-
27
- def load_organisms_and_endos(self):
28
- # This method should populate the organisms_to_endos dictionary
29
- # You'll need to implement the logic to load this data from your database or files
30
- # For example:
31
- # self.organisms_to_endos = {
32
- # "E. coli": ["Cas9", "Cas12a"],
33
- # "S. cerevisiae": ["Cas9"],
34
- # # ... other organisms and their associated endonucleases
35
- # }
36
- pass
37
 
38
  def get_organisms(self):
39
- # Return the list of organisms
40
  return list(self.organisms_to_endos.keys())
41
 
42
  def get_endos_for_organism(self, organism):
43
- # Return the list of endonucleases for a given organism
44
  return self.organisms_to_endos.get(organism, [])
45
 
46
  def set_files(self, organism, endo):
47
- self.cspr_file = self.organisms_to_files[organism][endo][0]
48
- self.db_file = self.organisms_to_files[organism][endo][1]
 
 
49
 
50
- def get_kstats(self):
51
- kstats = []
52
- with open(self.cspr_file, "r") as f:
53
- for line in f:
54
- if "KARYSTATS" in line:
55
- kstats = line.replace("KARYSTATS: ", "").strip().split(',')[:-1]
56
- break
57
- return kstats
58
 
59
  def get_repeats_data(self):
60
- conn = sqlite3.connect(self.db_file)
61
- c = conn.cursor()
62
-
63
- if self.row_limit == -1:
64
- sql_query = "SELECT * FROM repeats ORDER BY count DESC;"
65
- else:
66
- sql_query = f"SELECT * FROM repeats ORDER BY count DESC LIMIT 0, {self.row_limit};"
67
-
68
- data = c.execute(sql_query).fetchall()
69
- c.close()
70
- conn.close()
71
- return data
72
 
73
  def get_seed_data(self, seed):
74
- conn = sqlite3.connect(self.db_file)
75
- c = conn.cursor()
76
- data = c.execute("SELECT chromosome, location, pam, score, five, three FROM repeats WHERE seed = ?", (seed,)).fetchone()
77
- c.close()
78
- conn.close()
79
- return data
80
-
81
- def process_seed_data(self, seed_data, kstats):
82
- chromo, pos, pam, score, five, three = seed_data
83
- chromo = chromo.split(',')
84
- pos = pos.split(',')
85
- pam = pam.split(',')
86
- score = score.split(',')
87
- five = five.split(',')
88
- three = three.split(',')
89
-
90
- seed_data = {}
91
- event_data = {}
92
- for i in range(len(chromo)):
93
- curr_chromo = int(chromo[i])
94
- dir = "+" if int(pos[i]) >= 0 else "-"
95
- normalized_location = abs(float(pos[i]) / float(kstats[curr_chromo - 1]))
96
- if curr_chromo in seed_data:
97
- seed_data[curr_chromo].append(normalized_location)
98
- event_data[curr_chromo].append([normalized_location, pos[i], five[i] + seed + three[i], pam[i], score[i], dir])
99
- else:
100
- seed_data[curr_chromo] = [normalized_location]
101
- event_data[curr_chromo] = [[normalized_location, pos[i], five[i] + seed + three[i], pam[i], score[i], dir]]
102
-
103
- return seed_data, event_data
104
 
105
  def get_chro_bar_data(self, seed):
106
- conn = sqlite3.connect(self.db_file)
107
- c = conn.cursor()
108
- data = c.execute("SELECT chromosome FROM repeats WHERE seed = ?", (seed,)).fetchone()
109
- c.close()
110
- conn.close()
111
-
112
- data = [int(x) for x in data[0].split(',')]
113
- bar_data = Counter(data)
114
- return bar_data
115
 
116
  def get_seeds_vs_repeats_data(self):
117
- conn = sqlite3.connect(self.db_file)
118
- c = conn.cursor()
119
- data = c.execute("select count, COUNT(count) as cnt from repeats group by count order by cnt DESC;").fetchall()
120
- c.close()
121
- conn.close()
122
- return data
123
 
124
  def get_repeats_vs_seeds_data(self):
125
- conn = sqlite3.connect(self.db_file)
126
- c = conn.cursor()
127
- data = c.execute("SELECT count from repeats;").fetchall()
128
- c.close()
129
- conn.close()
130
-
131
- y1 = [row[0] for row in data]
132
- self.average = statistics.mean(y1)
133
- self.mode = statistics.mode(y1)
134
- self.median = statistics.median(y1)
135
- self.repeat_count = len(y1)
136
-
137
- return y1
 
 
 
 
138
 
139
- def set_row_limit(self, limit):
140
- self.row_limit = limit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
 
 
 
 
2
 
3
  class MultitargetingWindowModel:
4
  def __init__(self, global_settings):
5
+ self.settings = global_settings
6
+ self.logger = global_settings.get_logger()
7
+
8
  self.cspr_file = ""
9
  self.db_file = ""
 
 
 
 
 
 
 
 
 
 
10
  self.row_limit = 1000
11
+
12
+ # Get organism and endo mappings from DatabaseManager
13
+ self.organisms_to_files, self.organisms_to_endos = self.settings.db_manager.get_organisms_and_endos()
 
 
 
 
 
 
 
 
 
 
14
 
15
  def get_organisms(self):
16
+ """Get list of available organisms"""
17
  return list(self.organisms_to_endos.keys())
18
 
19
  def get_endos_for_organism(self, organism):
20
+ """Get available endonucleases for given organism"""
21
  return self.organisms_to_endos.get(organism, [])
22
 
23
  def set_files(self, organism, endo):
24
+ """Set the CSPR and DB files for analysis"""
25
+ if not organism or not endo:
26
+ self.logger.error("Organism or endonuclease not specified")
27
+ raise ValueError("Organism and endonuclease must be specified")
28
 
29
+ self.cspr_file = self._get_cspr_file_path(organism, endo)
30
+ self.db_file = self._get_db_file_path(organism, endo)
31
+
32
+ if not self.cspr_file or not self.db_file:
33
+ raise FileNotFoundError("Required files not found")
 
 
 
34
 
35
  def get_repeats_data(self):
36
+ """Get repeats data for the seeds table"""
37
+ if not self.db_file:
38
+ raise ValueError("Database file not set. Please select an organism and endonuclease first.")
39
+ return self.settings.db_manager.get_repeats_data(self.db_file, self.row_limit)
 
 
 
 
 
 
 
 
40
 
41
  def get_seed_data(self, seed):
42
+ """Get detailed data for a specific seed"""
43
+ return self.settings.db_manager.get_seed_data(self.db_file, seed)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  def get_chro_bar_data(self, seed):
46
+ """Get chromosome distribution data for a seed"""
47
+ return self.settings.db_manager.get_chro_bar_data(self.db_file, seed)
 
 
 
 
 
 
 
48
 
49
  def get_seeds_vs_repeats_data(self):
50
+ """Get data for seeds vs repeats plot"""
51
+ return self.settings.db_manager.get_seeds_vs_repeats_data(self.db_file)
 
 
 
 
52
 
53
  def get_repeats_vs_seeds_data(self):
54
+ """Get data for repeats vs seeds plot"""
55
+ return self.settings.db_manager.get_repeats_vs_seeds_data(self.db_file)
56
+
57
+ def calculate_statistics(self):
58
+ """Calculate global statistics"""
59
+ return self.settings.db_manager.calculate_statistics(self.db_file)
60
+
61
+ def get_sql_settings(self):
62
+ """Get current SQL query settings"""
63
+ return {
64
+ 'row_limit': self.row_limit
65
+ }
66
+
67
+ def update_sql_settings(self, settings):
68
+ """Update SQL query settings"""
69
+ if 'row_limit' in settings:
70
+ self.row_limit = settings['row_limit']
71
 
72
+ # Keep the file path methods as they are specific to this model
73
+ def _get_cspr_file_path(self, organism, endo):
74
+ """Get path to CSPR file for organism/endo combination"""
75
+ try:
76
+ if organism not in self.organisms_to_files or endo not in self.organisms_to_files[organism]:
77
+ self.logger.error(f"No CSPR file mapping found for {organism} with {endo}")
78
+ return None
79
+
80
+ file_name = self.organisms_to_files[organism][endo][0]
81
+ cspr_path = os.path.join(self.settings.get_db_path(), file_name)
82
+
83
+ if not os.path.exists(cspr_path):
84
+ self.logger.error(f"CSPR file not found: {cspr_path}")
85
+ return None
86
+
87
+ return cspr_path
88
+ except Exception as e:
89
+ self.logger.error(f"Error getting CSPR file path: {str(e)}")
90
+ return None
91
+
92
+ def _get_db_file_path(self, organism, endo):
93
+ """Get path to DB file for organism/endo combination"""
94
+ try:
95
+ if organism not in self.organisms_to_files or endo not in self.organisms_to_files[organism]:
96
+ self.logger.error(f"No DB file mapping found for {organism} with {endo}")
97
+ return None
98
+
99
+ file_name = self.organisms_to_files[organism][endo][1]
100
+ db_path = os.path.join(self.settings.get_db_path(), file_name)
101
+
102
+ if not os.path.exists(db_path):
103
+ self.logger.error(f"Database file not found: {db_path}")
104
+ return None
105
+
106
+ return db_path
107
+ except Exception as e:
108
+ self.logger.error(f"Error getting database file path: {str(e)}")
109
+ return None
110
+
111
+ def get_kstats(self):
112
+ """Get kstats from CSPR file"""
113
+ try:
114
+ if not self.cspr_file:
115
+ self.logger.error("CSPR file not set")
116
+ raise ValueError("CSPR file not set. Please select an organism and endonuclease first.")
117
+
118
+ kstats = []
119
+ with open(self.cspr_file, "r") as f:
120
+ for line in f:
121
+ if "KARYSTATS" in line:
122
+ kstats = line.replace("KARYSTATS: ", "").strip().split(',')[:-1]
123
+ break
124
+
125
+ if not kstats:
126
+ raise ValueError("No KARYSTATS found in CSPR file")
127
+
128
+ return kstats
129
+
130
+ except Exception as e:
131
+ self.logger.error(f"Error getting kstats: {str(e)}")
132
+ raise
src/models/NCBIWindowModel.py CHANGED
@@ -10,6 +10,8 @@ import platform
10
  import warnings
11
  import xml.etree.ElementTree as ET
12
  from PyQt6.QtCore import Qt
 
 
13
 
14
  class NCBIWindowModel:
15
  def __init__(self, settings):
@@ -20,6 +22,9 @@ class NCBIWindowModel:
20
  self.refseq_ftp_dict = {}
21
  self.files = [] # Initialize the files list
22
  Entrez.email = "your_email@example.com" # Replace with a valid email
 
 
 
23
 
24
  # Suppress the XMLParsedAsHTMLWarning
25
  warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
@@ -230,3 +235,118 @@ class CustomProxyModel(QtCore.QSortFilterProxyModel):
230
  if regex.match(text).hasMatch():
231
  return False
232
  return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  import warnings
11
  import xml.etree.ElementTree as ET
12
  from PyQt6.QtCore import Qt
13
+ import socket
14
+ from urllib.parse import urlparse
15
 
16
  class NCBIWindowModel:
17
  def __init__(self, settings):
 
22
  self.refseq_ftp_dict = {}
23
  self.files = [] # Initialize the files list
24
  Entrez.email = "your_email@example.com" # Replace with a valid email
25
+
26
+ # Make DownloadThread accessible through the model
27
+ self.DownloadThread = DownloadThread
28
 
29
  # Suppress the XMLParsedAsHTMLWarning
30
  warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
 
235
  if regex.match(text).hasMatch():
236
  return False
237
  return True
238
+
239
+ class DownloadThread(QtCore.QThread):
240
+ finished = QtCore.pyqtSignal(bool)
241
+ progress_updated = QtCore.pyqtSignal(int, int, int)
242
+ status_updated = QtCore.pyqtSignal(str)
243
+ all_completed = QtCore.pyqtSignal()
244
+
245
+ def __init__(self, controller, url, id, species_name, strain, download_fna, download_gbff):
246
+ super().__init__()
247
+ self.controller = controller
248
+ self.url = url
249
+ self.id = id
250
+ self.species_name = species_name
251
+ self.strain = strain
252
+ self.download_fna = download_fna
253
+ self.download_gbff = download_gbff
254
+
255
+ def run(self):
256
+ try:
257
+ parsed_url = urlparse(self.url)
258
+ ftp_host = parsed_url.netloc
259
+ ftp_path = parsed_url.path
260
+
261
+ self.controller.logger.info(f"Attempting to connect to FTP server: {ftp_host}")
262
+
263
+ try:
264
+ ip_address = socket.gethostbyname(ftp_host)
265
+ self.controller.logger.info(f"Resolved IP address: {ip_address}")
266
+ except socket.gaierror as e:
267
+ self.controller.logger.error(f"Failed to resolve hostname: {ftp_host}. Error: {str(e)}")
268
+ self.finished.emit(False)
269
+ return
270
+
271
+ ftp = FTP(ftp_host)
272
+ ftp.login()
273
+ ftp.cwd(ftp_path)
274
+ ftp.set_pasv(True)
275
+
276
+ # Set binary mode before any operations
277
+ ftp.voidcmd('TYPE I')
278
+
279
+ files_to_download = []
280
+
281
+ # Get list of all files
282
+ all_files = ftp.nlst()
283
+
284
+ # Process FNA files if requested
285
+ if self.download_fna:
286
+ # Find the main genomic FNA file (should be exactly one)
287
+ genomic_fna = [f for f in all_files
288
+ if f.endswith('_genomic.fna.gz')
289
+ and not any(x in f for x in ['cds_from', 'rna_from'])]
290
+
291
+ if genomic_fna:
292
+ files_to_download.append(genomic_fna[0])
293
+ self.controller.logger.info(f"Found main genomic FNA file: {genomic_fna[0]}")
294
+
295
+ # Process GBFF files if requested
296
+ if self.download_gbff:
297
+ gbff_files = [f for f in all_files if f.endswith('_genomic.gbff.gz')]
298
+ files_to_download.extend(gbff_files)
299
+ self.controller.logger.info(f"Found GBFF files: {gbff_files}")
300
+
301
+ # Calculate total size with error handling
302
+ total_size = 0
303
+ for file in files_to_download:
304
+ try:
305
+ size = ftp.size(file)
306
+ if size is not None:
307
+ total_size += size
308
+ except Exception as e:
309
+ self.controller.logger.warning(f"Could not get size for file {file}: {str(e)}")
310
+
311
+ downloaded_size = 0
312
+
313
+ # Download files
314
+ for file in files_to_download:
315
+ try:
316
+ self.status_updated.emit(f"Downloading: {file}")
317
+ file_type = 'FNA' if file.endswith('.fna.gz') else 'GBFF'
318
+ output_dir = os.path.join(self.controller.settings.CSPR_DB, file_type)
319
+ os.makedirs(output_dir, exist_ok=True)
320
+
321
+ local_filename = os.path.join(output_dir, file)
322
+ self.controller.logger.info(f"Downloading file: {file} to {local_filename}")
323
+
324
+ with open(local_filename, 'wb') as local_file:
325
+ def callback(data):
326
+ local_file.write(data)
327
+ nonlocal downloaded_size
328
+ downloaded_size += len(data)
329
+ if total_size > 0:
330
+ self.progress_updated.emit(self.id, downloaded_size, total_size)
331
+
332
+ ftp.retrbinary(f"RETR {file}", callback)
333
+
334
+ self.controller.logger.info(f"Download complete: {file}")
335
+ self.status_updated.emit(f"Decompressing: {file}")
336
+
337
+ self.controller.model.decompress_file(local_filename)
338
+ decompressed_filename = local_filename[:-3]
339
+ self.controller.model.add_downloaded_file(decompressed_filename)
340
+
341
+ except Exception as e:
342
+ self.controller.logger.error(f"Error downloading file {file}: {str(e)}")
343
+ continue
344
+
345
+ ftp.quit()
346
+ self.controller.logger.info(f"All files downloaded and decompressed successfully for ID: {self.id}")
347
+ self.all_completed.emit()
348
+ self.finished.emit(True)
349
+
350
+ except Exception as e:
351
+ self.controller.logger.error(f"Download error for ID {self.id}: {str(e)}", exc_info=True)
352
+ self.finished.emit(False)
src/models/NewGenomeWindowModel.py CHANGED
@@ -88,9 +88,9 @@ class NewGenomeWindowModel(QObject):
88
  f'{db_path}',
89
  f'{self.settings.get_casper_info_path()}',
90
  f'{file_path}',
91
- f'{organism_name} {strain}',
92
  'notes',
93
- f'"DATA:{endonuclease_data["endonuclease_on_target_scoring"]}"'
94
  ]
95
  return arguments
96
 
 
88
  f'{db_path}',
89
  f'{self.settings.get_casper_info_path()}',
90
  f'{file_path}',
91
+ f'"{organism_name} {strain}"',
92
  'notes',
93
+ f'DATA:{endonuclease_data["endonuclease_on_target_scoring"]}'
94
  ]
95
  return arguments
96
 
src/models/PopulationAnalysisWindowModel.py CHANGED
@@ -5,8 +5,9 @@ from utils.ui import show_error
5
 
6
  class PopulationAnalysisWindowModel:
7
  def __init__(self, global_settings):
8
- self.global_settings = global_settings
9
- self.app_dir = global_settings.get_app_dir()
 
10
  self.cspr_files = []
11
  self.db_files = []
12
  self.org_names = {}
@@ -16,37 +17,75 @@ class PopulationAnalysisWindowModel:
16
  self.index_to_db = {}
17
 
18
  def load_endonucleases(self):
19
- endos = {}
20
  try:
21
- with open(self.global_settings.get_casper_info_path(), 'r') as f:
22
- for line in f:
23
- if line.startswith('ENDONUCLEASES'):
24
- for line in f:
25
- if line.startswith('-'):
26
- break
27
- line_tokens = line.strip().split(';')
28
- endo = line_tokens[0]
29
- pam = line_tokens[1].split(',')[0] if ',' in line_tokens[1] else line_tokens[1]
30
- default_five_length = line_tokens[2]
31
- default_seed_length = line_tokens[3]
32
- default_three_length = line_tokens[4]
33
- endos[f"{endo} PAM: {pam}"] = (endo, pam, default_five_length, default_seed_length, default_three_length)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  except Exception as e:
35
- show_error(self.global_settings, "Error loading endonucleases", str(e))
36
- return endos
 
 
37
 
38
- def get_organism_files(self, endo):
 
39
  org_files = []
40
  try:
41
- for file in os.listdir(self.global_settings.CSPR_DB):
42
- if file.endswith('.cspr') and file[file.rfind('_') + 1:file.find('.cspr')] == endo:
43
- cspr_file = os.path.join(self.global_settings.CSPR_DB, file)
44
- db_file = cspr_file.replace(".cspr", "_repeats.db")
45
- with open(cspr_file, 'r') as f:
46
- org_name = f.readline().split(":")[-1].strip()
47
- org_files.append((org_name, cspr_file, db_file))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  except Exception as e:
49
- show_error(self.global_settings, "Error getting organism files", str(e))
 
50
  return org_files
51
 
52
  def get_shared_seeds(self, db_files, limit=False):
@@ -131,6 +170,11 @@ class PopulationAnalysisWindowModel:
131
  locations = []
132
  try:
133
  for db_file in db_files:
 
 
 
 
 
134
  with sqlite3.connect(db_file) as conn:
135
  c = conn.cursor()
136
  for seed in seeds:
@@ -145,15 +189,25 @@ class PopulationAnalysisWindowModel:
145
  locations.append({
146
  'seed': seed,
147
  'sequence': sequence,
148
- 'organism': os.path.basename(db_file).split('_')[0],
149
  'chromosome': chrom,
150
  'location': abs(int(locs[i]))
151
  })
 
152
  except Exception as e:
 
153
  show_error(self.global_settings, "Error getting seed locations", str(e))
154
  return locations
155
 
156
  def get_org_names(self):
157
- # Implement this method to populate self.org_names
158
- pass
 
 
 
 
 
 
 
 
159
 
 
5
 
6
  class PopulationAnalysisWindowModel:
7
  def __init__(self, global_settings):
8
+ self.settings = global_settings
9
+ self.logger = self.settings.get_logger()
10
+ self.app_dir = self.settings.get_app_dir_path()
11
  self.cspr_files = []
12
  self.db_files = []
13
  self.org_names = {}
 
17
  self.index_to_db = {}
18
 
19
  def load_endonucleases(self):
20
+ """Load endonucleases from GlobalSettings"""
21
  try:
22
+ self.logger.info("Starting load_endonucleases()")
23
+
24
+ # Get endonucleases from global settings
25
+ endos = self.settings.get_endonucleases()
26
+ self.logger.debug(f"Raw endonucleases from settings: {endos}")
27
+
28
+ if not endos:
29
+ self.logger.warning("No endonucleases returned from settings")
30
+ return {}
31
+
32
+ # Format the endonucleases for display
33
+ formatted_endos = {}
34
+ for endo, data in endos.items():
35
+ self.logger.debug(f"Processing endo: {endo}, data: {data}")
36
+ pam = data.get('pam', '').strip()
37
+ # Remove any extra "PAM:" text that might be in the PAM string
38
+ pam = pam.replace('PAM:', '').strip()
39
+ # Create display name without duplicate "PAM:" text
40
+ display_name = f"{endo}"
41
+
42
+ formatted_endos[display_name] = (endo, pam,
43
+ data.get('default_five_length', ''),
44
+ data.get('default_seed_length', ''),
45
+ data.get('default_three_length', ''))
46
+
47
+ self.logger.info(f"Successfully formatted {len(formatted_endos)} endonucleases")
48
+ self.logger.debug(f"Formatted endonucleases: {formatted_endos}")
49
+ return formatted_endos
50
+
51
  except Exception as e:
52
+ self.logger.error(f"Error loading endonucleases: {str(e)}")
53
+ self.logger.exception("Full traceback:")
54
+ show_error(self.settings, "Error loading endonucleases", str(e))
55
+ return {}
56
 
57
+ def get_organism_files(self, endo_display_name):
58
+ """Get organism files for selected endonuclease using DatabaseManager"""
59
  org_files = []
60
  try:
61
+ # Extract just the endonuclease name from the display text (remove PAM)
62
+ endo = endo_display_name.split(" - PAM:")[0].strip()
63
+ self.logger.info(f"Getting organism files for endonuclease: {endo}")
64
+
65
+ # Get organism mappings from database manager
66
+ organisms_to_files, organisms_to_endos = self.settings.db_manager.get_organisms_and_endos()
67
+
68
+ # Process each organism that has this endonuclease
69
+ for organism, endos in organisms_to_endos.items():
70
+ if endo in endos:
71
+ cspr_file = os.path.join(self.settings.CSPR_DB, organisms_to_files[organism][endo][0])
72
+ db_file = os.path.join(self.settings.CSPR_DB, organisms_to_files[organism][endo][1])
73
+
74
+ if not os.path.exists(db_file):
75
+ self.logger.warning(f"Database file not found: {db_file}")
76
+ continue
77
+
78
+ org_files.append((organism, cspr_file, db_file))
79
+
80
+ # Store the mapping for later use
81
+ index = len(org_files) - 1
82
+ self.index_to_cspr[index] = cspr_file
83
+ self.index_to_db[index] = db_file
84
+
85
+ self.logger.info(f"Found {len(org_files)} organism files")
86
  except Exception as e:
87
+ self.logger.error(f"Error getting organism files: {str(e)}")
88
+ show_error(self.settings, "Error getting organism files", str(e))
89
  return org_files
90
 
91
  def get_shared_seeds(self, db_files, limit=False):
 
170
  locations = []
171
  try:
172
  for db_file in db_files:
173
+ # Get organism name from CSPR file
174
+ cspr_file = db_file.replace("_repeats.db", ".cspr")
175
+ with open(cspr_file, 'r') as f:
176
+ organism_name = f.readline().split(":")[-1].strip()
177
+
178
  with sqlite3.connect(db_file) as conn:
179
  c = conn.cursor()
180
  for seed in seeds:
 
189
  locations.append({
190
  'seed': seed,
191
  'sequence': sequence,
192
+ 'organism': organism_name,
193
  'chromosome': chrom,
194
  'location': abs(int(locs[i]))
195
  })
196
+ self.logger.debug(f"Found {len(locations)} locations")
197
  except Exception as e:
198
+ self.logger.error(f"Error getting seed locations: {str(e)}")
199
  show_error(self.global_settings, "Error getting seed locations", str(e))
200
  return locations
201
 
202
  def get_org_names(self):
203
+ try:
204
+ self.org_names = {}
205
+ for i, cspr_file in self.index_to_cspr.items():
206
+ with open(cspr_file, 'r') as f:
207
+ org_name = f.readline().split(":")[-1].strip()
208
+ self.org_names[i] = org_name
209
+ self.logger.info(f"Loaded {len(self.org_names)} organism names")
210
+ except Exception as e:
211
+ self.logger.error(f"Error getting organism names: {str(e)}")
212
+ show_error(self.settings, "Error getting organism names", str(e))
213
 
src/models/ViewTargetsModel.py CHANGED
@@ -4,13 +4,23 @@ from models.AnnotationParser import AnnotationParser
4
  import os
5
  from Bio import SeqIO
6
  from Bio.Seq import Seq
 
 
 
 
 
 
 
 
 
 
7
 
8
  class ViewTargetsModel(HomeWindowModel):
9
  def __init__(self, global_settings):
10
  super().__init__(global_settings)
11
  self.targets = []
12
  self.cspr_parser = None
13
- self.annotation_parser = AnnotationParser(global_settings)
14
  self.gene_sequence = ""
15
  self.highlighted_sequence = ""
16
  self.gene_info = {}
@@ -22,175 +32,286 @@ class ViewTargetsModel(HomeWindowModel):
22
  self.current_gene_end = 0
23
  self.extended_sequence = ""
24
  self.chromosome = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  def load_targets(self, selected_targets, organism, endonuclease):
27
- org_files = self.get_organism_to_files()
28
- if organism in org_files and endonuclease in org_files[organism]:
29
- cspr_file = org_files[organism][endonuclease][0]
30
- cspr_path = os.path.join(self.global_settings.get_db_path(), cspr_file)
31
- self.cspr_parser = CSPRparser(cspr_path, self.global_settings.get_casper_info_path())
 
 
 
 
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  self.targets = []
34
- self.available_genes = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  for target in selected_targets:
36
- self.chromosome = target['chromosome'] # Store the chromosome
37
  chrom = target['chromosome']
38
  start, end = map(int, target['location'].split('-'))
39
- pos_tuple = (chrom, start, end)
40
- cspr_targets = self.cspr_parser.read_targets(target['feature_name'], pos_tuple, endonuclease)
41
- self.targets.extend(cspr_targets)
42
- if target['feature_name'] not in self.available_genes:
43
- self.available_genes.append(target['feature_name'])
44
-
45
- if self.available_genes:
46
- self._load_gene_data(self.available_genes[0])
47
-
48
- def _load_gene_data(self, gene_name):
49
- annotation_files = self.get_annotation_files()
50
- if annotation_files:
51
- self.annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_files[0])
52
- self.annotation_parser.set_annotation_file(self.annotation_path)
53
- gene_data = self.annotation_parser.get_gene_data(gene_name)
54
- print("gene_data", gene_data)
55
- if gene_data:
56
- self.gene_sequence = gene_data.get('sequence', '')
57
- self.gene_info = gene_data.get('info', {})
58
 
59
- location = self.gene_info.get('feature_location', '')
60
- if location:
61
- start, end, strand = self._parse_location(location)
62
- print("strand", strand)
63
- self.current_gene_start = start + 1
64
- self.current_gene_end = end
65
-
66
- # Fetch the extended sequence
67
- extended_start = max(0, start - 30)
68
- extended_end = end + 30
69
- extended_seq = self.get_sequence_from_annotation(self.chromosome, extended_start, extended_end)
70
-
71
- if strand == '-':
72
- # Reverse complement the entire extended sequence
73
- self.gene_sequence = str(Seq(self.gene_sequence).reverse_complement())
74
- print("self.gene_sequence", self.gene_sequence)
75
-
76
- # Create the extended sequence with lowercase placeholders
77
- left_placeholder = extended_seq[:30].lower()
78
- right_placeholder = extended_seq[-30:].lower()
79
- self.gene_sequence = extended_seq[30:-30]
80
- self.extended_sequence = f"{left_placeholder}{self.gene_sequence}{right_placeholder}"
81
 
82
- self.global_settings.logger.debug(f"Loaded gene sequence for {gene_name}: {self.extended_sequence[:50]}...")
83
- else:
84
- self.global_settings.logger.warning(f"Gene {gene_name} not found in annotation file")
 
 
 
 
 
 
 
85
 
86
- def _parse_location(self, location):
87
- parts = location.split(':')
88
- start = parts[0]
89
- end_strand = parts[1]
90
- end = end_strand[:-3] # Remove the last 3 characters (strand info)
91
- strand = end_strand[-2:-1] # Get the strand info (last 2 characters, excluding the closing parenthesis)
92
- return int(start), int(end), strand
93
 
94
- def get_targets(self):
95
- return self.targets
 
 
 
 
96
 
97
- def perform_off_target_analysis(self, selected_targets):
98
- # Implement off-target analysis here
99
- pass
100
 
101
- def perform_cotargeting(self, selected_targets, endonucleases):
102
- # Implement cotargeting analysis here
103
- pass
 
 
 
 
 
 
 
 
 
104
 
105
- def load_gene_viewer_data(self):
106
- annotation_files = self.get_annotation_files()
107
- if annotation_files:
108
- self.annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_files[0])
109
- self.annotation_parser.set_annotation_file(self.annotation_path)
110
- self.available_genes = [target['feature_name'] for target in self.targets]
111
- else:
112
- self.global_settings.logger.warning("No annotation files found.")
113
- self.available_genes = []
114
 
115
  def get_gene_data(self, gene_name):
116
- self._load_gene_data(gene_name)
117
- return {
118
- 'sequence': self.extended_sequence,
119
- 'info': self.gene_info,
120
- 'start': self.current_gene_start,
121
- 'end': self.current_gene_end
122
- }
123
-
124
- def update_gene_viewer_indices(self, start, end):
125
- if start < end and start >= self.current_gene_start and end <= self.current_gene_end:
126
- relative_start = start - self.current_gene_start + 30 # Add 30 to account for the left placeholder
127
- relative_end = end - self.current_gene_start + 30
128
- self.extended_sequence = self.extended_sequence[:30] + self.extended_sequence[relative_start:relative_end] + self.extended_sequence[-30:]
129
- return True
130
- return False
131
-
132
- def reset_gene_viewer_indices(self):
133
- if self.available_genes:
134
- self._load_gene_data(self.available_genes[0])
135
-
136
- def get_available_genes(self):
137
- return self.available_genes
138
-
139
- def get_gene_sequence(self):
140
- return self.gene_sequence
141
 
142
- def get_highlighted_gene_sequence(self):
143
- return self.highlighted_sequence
144
 
145
  def highlight_targets_in_gene_viewer(self, selected_targets):
146
- highlighted_sequence = self.extended_sequence
147
- for target in selected_targets:
148
- sequence = target['sequence']
149
- strand = target['strand']
150
-
151
- if strand == '+':
152
- index = highlighted_sequence.upper().find(sequence.upper())
153
- if index != -1:
154
- highlighted_sequence = (
155
- highlighted_sequence[:index] +
156
- f"<span style='background-color: green;'>{highlighted_sequence[index:index+len(sequence)]}</span>" +
157
- highlighted_sequence[index+len(sequence):]
158
- )
159
- else: # strand == '-'
160
- rev_comp_sequence = str(Seq(sequence).reverse_complement())
161
- index = highlighted_sequence.upper().find(rev_comp_sequence.upper())
162
- if index != -1:
163
- highlighted_sequence = (
164
- highlighted_sequence[:index] +
165
- f"<span style='background-color: red;'>{highlighted_sequence[index:index+len(rev_comp_sequence)]}</span>" +
166
- highlighted_sequence[index+len(rev_comp_sequence):]
167
- )
168
-
169
- self.highlighted_sequence = highlighted_sequence
170
- return self.highlighted_sequence
171
 
172
- def get_filter_options(self):
173
- return self.filter_options
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- def set_filter_options(self, options):
176
- self.filter_options = options
 
177
 
178
- def get_scoring_options(self):
179
- return self.scoring_options
180
 
181
- def set_scoring_options(self, options):
182
- self.scoring_options = options
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- def get_filtered_targets(self):
185
- # Implement filtering logic based on self.filter_options
186
- return self.targets
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
- def export_targets(self, selected_targets, file_path):
189
- # Implement export logic here
190
- pass
191
 
192
- def get_sequence_from_annotation(self, chrom, start, end):
193
- for record in SeqIO.parse(self.annotation_path, 'genbank'):
194
- if record.id == chrom:
195
- return str(record.seq[start:end])
196
- return ""
 
 
 
 
 
4
  import os
5
  from Bio import SeqIO
6
  from Bio.Seq import Seq
7
+ from functools import lru_cache
8
+ import threading
9
+ from collections import defaultdict
10
+ import re
11
+ import traceback
12
+ import time
13
+ import logging
14
+ from multiprocessing import Pool
15
+ from functools import partial
16
+ from multiprocessing import cpu_count
17
 
18
  class ViewTargetsModel(HomeWindowModel):
19
  def __init__(self, global_settings):
20
  super().__init__(global_settings)
21
  self.targets = []
22
  self.cspr_parser = None
23
+ self.annotation_parser = None # Will be initialized when needed
24
  self.gene_sequence = ""
25
  self.highlighted_sequence = ""
26
  self.gene_info = {}
 
32
  self.current_gene_end = 0
33
  self.extended_sequence = ""
34
  self.chromosome = ""
35
+
36
+ # Cache structures
37
+ self._gene_data_cache = {}
38
+ self._sequence_cache = {}
39
+ self._parser_cache = {}
40
+ self._chromosome_seqs = {}
41
+ self._cached_targets = {} # Add cache for targets
42
+
43
+ def cleanup(self):
44
+ """Cleanup method to be called when the view is closed"""
45
+ try:
46
+ # Disconnect from annotation file changes
47
+ if hasattr(self, '_annotation_signal'):
48
+ self.global_settings.annotation_file_changed.disconnect(self._on_annotation_file_changed)
49
+ self.global_settings.logger.debug("ViewTargetsModel disconnected from annotation file changes")
50
+
51
+ # Clear caches
52
+ self._gene_data_cache.clear()
53
+ self._sequence_cache.clear()
54
+ self._parser_cache.clear()
55
+
56
+ except Exception as e:
57
+ self.global_settings.logger.error(f"Error in ViewTargetsModel cleanup: {str(e)}")
58
+
59
+ def _on_annotation_file_changed(self, new_annotation_file):
60
+ """Clear all caches when annotation file changes"""
61
+ try:
62
+ self.global_settings.logger.debug(f"ViewTargetsModel clearing caches for new annotation file: {new_annotation_file}")
63
+ self._gene_data_cache.clear()
64
+ self._sequence_cache.clear()
65
+ self._parser_cache.clear()
66
+
67
+ # Update annotation path and parser
68
+ self.annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', new_annotation_file)
69
+ self.annotation_parser = AnnotationParser(self.global_settings)
70
+ self.annotation_parser.set_annotation_file(self.annotation_path)
71
+
72
+ # Clear other stored data
73
+ self.gene_sequence = ""
74
+ self.highlighted_sequence = ""
75
+ self.gene_info = {}
76
+ self.available_genes = []
77
+ self._chromosome_seqs = {}
78
+
79
+ except Exception as e:
80
+ self.global_settings.logger.error(f"Error in _on_annotation_file_changed: {str(e)}")
81
 
82
  def load_targets(self, selected_targets, organism, endonuclease):
83
+ """Fast target loading with minimal file operations"""
84
+ start_time = time.time()
85
+
86
+ try:
87
+ self.global_settings.logger.debug(f"Starting load_targets with {len(selected_targets)} targets")
88
+
89
+ # Store organism and endonuclease for potential reloading
90
+ self.organism = organism
91
+ self.endonuclease = endonuclease
92
 
93
+ # Get CSPR parser from cache or create new one
94
+ parser_start = time.time()
95
+ cspr_key = f"{organism}_{endonuclease}"
96
+ if cspr_key in self._parser_cache:
97
+ self.cspr_parser = self._parser_cache[cspr_key]
98
+ else:
99
+ org_files = self.get_organism_to_files()
100
+ if organism not in org_files or endonuclease not in org_files[organism]:
101
+ self.global_settings.logger.error(f"No CSPR file found for {organism} and {endonuclease}")
102
+ return
103
+
104
+ cspr_file = org_files[organism][endonuclease][0]
105
+ cspr_path = os.path.join(self.global_settings.get_db_path(), cspr_file)
106
+ self.cspr_parser = CSPRparser(cspr_path, self.global_settings.get_casper_info_path())
107
+ self._parser_cache[cspr_key] = self.cspr_parser
108
+ parser_time = time.time() - parser_start
109
+
110
+ # Initialize targets and genes
111
  self.targets = []
112
+ self.available_genes = set()
113
+
114
+ # Set up annotation parser if needed
115
+ if self.annotation_parser is None:
116
+ annotation_start = time.time()
117
+ self.annotation_parser = AnnotationParser(self.global_settings)
118
+ annotation_files = self.get_annotation_files()
119
+ if annotation_files:
120
+ self.annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_files[0])
121
+ self.annotation_parser.set_annotation_file(self.annotation_path)
122
+ annotation_time = time.time() - annotation_start
123
+ else:
124
+ annotation_time = 0
125
+
126
+ # Process targets in batches by chromosome
127
+ processing_start = time.time()
128
+
129
+ # Group targets by chromosome and prepare batch reading
130
+ batch_targets = defaultdict(list)
131
  for target in selected_targets:
 
132
  chrom = target['chromosome']
133
  start, end = map(int, target['location'].split('-'))
134
+ batch_targets[chrom].append({
135
+ 'feature_name': target['feature_name'],
136
+ 'start': start,
137
+ 'end': end
138
+ })
139
+ self.available_genes.add(target['feature_name'])
140
+
141
+ # Batch process targets for each chromosome
142
+ target_count = 0
143
+ for chrom, targets in batch_targets.items():
144
+ self.chromosome = chrom
 
 
 
 
 
 
 
 
145
 
146
+ # Sort targets by start position for more efficient reading
147
+ targets.sort(key=lambda x: x['start'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
+ # Read targets in a single batch per chromosome
150
+ batch_results = self.cspr_parser.read_targets_batch(
151
+ chromosome=chrom,
152
+ targets=targets,
153
+ endonuclease=endonuclease
154
+ )
155
+
156
+ if batch_results:
157
+ self.targets.extend(batch_results)
158
+ target_count += len(batch_results)
159
 
160
+ processing_time = time.time() - processing_start
 
 
 
 
 
 
161
 
162
+ # Convert genes to sorted list
163
+ self.available_genes = sorted(list(self.available_genes))
164
+
165
+ total_time = time.time() - start_time
166
+ self.global_settings.logger.debug(f"Total load_targets execution time: {total_time:.2f} seconds")
167
+ self.global_settings.logger.debug(f"Found {target_count} total CSPR targets")
168
 
169
+ except Exception as e:
170
+ self.global_settings.logger.error(f"Error in load_targets: {str(e)}\n{traceback.format_exc()}")
171
+ raise
172
 
173
+ def _get_chromosome_sequence(self, chromosome):
174
+ """Get chromosome sequence on demand"""
175
+ if not hasattr(self, '_chromosome_seqs'):
176
+ self._chromosome_seqs = {}
177
+
178
+ if chromosome not in self._chromosome_seqs:
179
+ for record in SeqIO.parse(self.annotation_path, "genbank"):
180
+ if record.id == chromosome:
181
+ self._chromosome_seqs[chromosome] = str(record.seq)
182
+ break
183
+
184
+ return self._chromosome_seqs.get(chromosome)
185
 
186
+ def _initialize_annotation_parser(self):
187
+ """Initialize annotation parser if not already initialized"""
188
+ if self.annotation_parser is None:
189
+ self.annotation_parser = AnnotationParser(self.global_settings)
190
+ if self.annotation_path:
191
+ self.annotation_parser.set_annotation_file(self.annotation_path)
 
 
 
192
 
193
  def get_gene_data(self, gene_name):
194
+ """Get gene data with caching"""
195
+ try:
196
+ if not gene_name:
197
+ self.global_settings.logger.error("No gene name provided")
198
+ return None
199
+
200
+ # Check model cache first
201
+ if gene_name in self._gene_data_cache:
202
+ return self._gene_data_cache[gene_name]
203
+
204
+ # Make sure annotation parser is initialized
205
+ if self.annotation_parser is None:
206
+ self._initialize_annotation_parser()
207
+
208
+ # Get gene data from parser
209
+ gene_data = self.annotation_parser.get_gene_data(gene_name)
210
+ if gene_data:
211
+ self._gene_data_cache[gene_name] = gene_data
212
+
213
+ return gene_data
214
+
215
+ except Exception as e:
216
+ self.global_settings.logger.error(f"Error getting gene data: {str(e)}")
217
+ return None
 
218
 
219
+ def get_targets(self):
220
+ return self.targets
221
 
222
  def highlight_targets_in_gene_viewer(self, selected_targets):
223
+ """Highlight selected targets in gene viewer"""
224
+ try:
225
+ self.global_settings.logger.debug("Starting highlight_targets_in_gene_viewer")
226
+ sequence = self.extended_sequence
227
+ if not sequence:
228
+ self.global_settings.logger.error("No extended sequence available")
229
+ return sequence
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
+ self.global_settings.logger.debug(f"Extended sequence length: {len(sequence)}")
232
+
233
+ # Sort targets by position for efficient highlighting
234
+ highlights = []
235
+ for target in selected_targets:
236
+ self.global_settings.logger.debug(f"Processing target: {target}")
237
+ sequence_to_find = target['sequence']
238
+ strand = target['strand']
239
+
240
+ # For negative strand, we need to use reverse complement
241
+ if strand == '-':
242
+ sequence_to_find = str(Seq(sequence_to_find).reverse_complement())
243
+ self.global_settings.logger.debug(f"Reverse complemented sequence: {sequence_to_find}")
244
+
245
+ # Search for the sequence in the gene viewer text
246
+ sequence_upper = sequence.upper()
247
+ target_upper = sequence_to_find.upper()
248
+
249
+ self.global_settings.logger.debug(f"Searching for sequence: {target_upper}")
250
+
251
+ # Find all occurrences
252
+ pos = sequence_upper.find(target_upper)
253
+ if pos != -1:
254
+ self.global_settings.logger.debug(f"Found sequence at position: {pos}")
255
+ color = 'red' if strand == '-' else 'green'
256
+ highlights.append((pos, len(sequence_to_find), color))
257
+ else:
258
+ self.global_settings.logger.warning(f"Sequence not found: {target_upper}")
259
 
260
+ if not highlights:
261
+ self.global_settings.logger.error("No sequences could be highlighted")
262
+ return sequence
263
 
264
+ self.global_settings.logger.debug(f"Found {len(highlights)} sequences to highlight")
 
265
 
266
+ # Build highlighted sequence
267
+ result = []
268
+ last_pos = 0
269
+ for pos, length, color in highlights:
270
+ result.append(sequence[last_pos:pos])
271
+ result.append(f"<span style='background-color: {color};'>")
272
+ result.append(sequence[pos:pos+length])
273
+ result.append("</span>")
274
+ last_pos = pos + length
275
+
276
+ result.append(sequence[last_pos:])
277
+ final_sequence = ''.join(result)
278
+
279
+ self.global_settings.logger.debug(f"Final highlighted sequence length: {len(final_sequence)}")
280
+ return final_sequence
281
 
282
+ except Exception as e:
283
+ self.global_settings.logger.error(f"Error highlighting targets: {str(e)}\n{traceback.format_exc()}")
284
+ return sequence
285
+
286
+ def get_available_genes(self):
287
+ """Get list of available genes from the loaded targets"""
288
+ try:
289
+ # Return the available genes list that was populated during load_targets
290
+ if hasattr(self, 'available_genes'):
291
+ return self.available_genes
292
+
293
+ # If not already populated, get unique genes from targets
294
+ genes = set()
295
+ for target in self.targets:
296
+ if 'feature_name' in target:
297
+ genes.add(target['feature_name'])
298
+
299
+ # Store for future use
300
+ self.available_genes = sorted(list(genes))
301
+ return self.available_genes
302
+
303
+ except Exception as e:
304
+ self.global_settings.logger.error(f"Error getting available genes: {str(e)}")
305
+ return []
306
 
307
+ # ... (other methods remain unchanged)
 
 
308
 
309
+ def _process_target(self, target):
310
+ """Process a single target - moved to separate method for parallel processing"""
311
+ try:
312
+ # Your existing target processing logic here
313
+ # Make sure to handle any shared resources thread-safely
314
+ pass
315
+ except Exception as e:
316
+ logging.error(f"Error processing target: {e}")
317
+ return None
src/ui/multitargeting_window.ui CHANGED
@@ -19,11 +19,19 @@
19
  <string>MainWindow</string>
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
 
 
 
 
 
 
 
 
22
  <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="1" column="1">
24
  <layout class="QGridLayout" name="gridLayout">
25
- <item row="2" column="0" rowspan="2">
26
- <widget class="QGroupBox" name="groupBox">
27
  <property name="sizePolicy">
28
  <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
29
  <horstretch>0</horstretch>
@@ -34,8 +42,8 @@
34
  <string>Select Organism and Endonuclease:</string>
35
  </property>
36
  <layout class="QGridLayout" name="gridLayout_8">
37
- <item row="2" column="0">
38
- <widget class="QComboBox" name="organism_drop">
39
  <property name="sizePolicy">
40
  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
41
  <horstretch>0</horstretch>
@@ -50,10 +58,10 @@
50
  </property>
51
  </widget>
52
  </item>
53
- <item row="3" column="0" colspan="2">
54
  <layout class="QHBoxLayout" name="horizontalLayout">
55
  <item>
56
- <widget class="QPushButton" name="Analyze_Button">
57
  <property name="sizePolicy">
58
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
59
  <horstretch>0</horstretch>
@@ -72,7 +80,7 @@
72
  </widget>
73
  </item>
74
  <item>
75
- <widget class="QToolButton" name="sql_query_settings">
76
  <property name="minimumSize">
77
  <size>
78
  <width>0</width>
@@ -86,8 +94,8 @@
86
  </item>
87
  </layout>
88
  </item>
89
- <item row="1" column="1">
90
- <widget class="QLabel" name="label_2">
91
  <property name="sizePolicy">
92
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
93
  <horstretch>0</horstretch>
@@ -99,8 +107,8 @@
99
  </property>
100
  </widget>
101
  </item>
102
- <item row="5" column="0" colspan="2">
103
- <widget class="QTableWidget" name="table">
104
  <property name="minimumSize">
105
  <size>
106
  <width>400</width>
@@ -109,8 +117,8 @@
109
  </property>
110
  </widget>
111
  </item>
112
- <item row="1" column="0">
113
- <widget class="QLabel" name="label">
114
  <property name="sizePolicy">
115
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
116
  <horstretch>0</horstretch>
@@ -122,8 +130,8 @@
122
  </property>
123
  </widget>
124
  </item>
125
- <item row="2" column="1">
126
- <widget class="QComboBox" name="endo_drop">
127
  <property name="sizePolicy">
128
  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
129
  <horstretch>0</horstretch>
@@ -138,40 +146,8 @@
138
  </property>
139
  </widget>
140
  </item>
141
- <item row="0" column="0" colspan="2">
142
- <spacer name="verticalSpacer_3">
143
- <property name="orientation">
144
- <enum>Qt::Vertical</enum>
145
- </property>
146
- <property name="sizeType">
147
- <enum>QSizePolicy::Fixed</enum>
148
- </property>
149
- <property name="sizeHint" stdset="0">
150
- <size>
151
- <width>10</width>
152
- <height>10</height>
153
- </size>
154
- </property>
155
- </spacer>
156
- </item>
157
- <item row="6" column="0" colspan="2">
158
- <spacer name="verticalSpacer_4">
159
- <property name="orientation">
160
- <enum>Qt::Vertical</enum>
161
- </property>
162
- <property name="sizeType">
163
- <enum>QSizePolicy::Fixed</enum>
164
- </property>
165
- <property name="sizeHint" stdset="0">
166
- <size>
167
- <width>10</width>
168
- <height>10</height>
169
- </size>
170
- </property>
171
- </spacer>
172
- </item>
173
- <item row="4" column="0">
174
- <widget class="QCheckBox" name="selectAll">
175
  <property name="text">
176
  <string>Select All</string>
177
  </property>
@@ -180,8 +156,8 @@
180
  </layout>
181
  </widget>
182
  </item>
183
- <item row="2" column="1">
184
- <widget class="QGroupBox" name="groupBox_2">
185
  <property name="sizePolicy">
186
  <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
187
  <horstretch>0</horstretch>
@@ -198,40 +174,8 @@
198
  <string>Seed Analysis</string>
199
  </property>
200
  <layout class="QGridLayout" name="gridLayout_6">
201
- <item row="3" column="0">
202
- <spacer name="verticalSpacer_8">
203
- <property name="orientation">
204
- <enum>Qt::Vertical</enum>
205
- </property>
206
- <property name="sizeType">
207
- <enum>QSizePolicy::Fixed</enum>
208
- </property>
209
- <property name="sizeHint" stdset="0">
210
- <size>
211
- <width>10</width>
212
- <height>10</height>
213
- </size>
214
- </property>
215
- </spacer>
216
- </item>
217
  <item row="0" column="0">
218
- <spacer name="verticalSpacer_7">
219
- <property name="orientation">
220
- <enum>Qt::Vertical</enum>
221
- </property>
222
- <property name="sizeType">
223
- <enum>QSizePolicy::Fixed</enum>
224
- </property>
225
- <property name="sizeHint" stdset="0">
226
- <size>
227
- <width>10</width>
228
- <height>10</height>
229
- </size>
230
- </property>
231
- </spacer>
232
- </item>
233
- <item row="1" column="0">
234
- <widget class="QTabWidget" name="tabWidget">
235
  <property name="sizePolicy">
236
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
237
  <horstretch>0</horstretch>
@@ -247,13 +191,13 @@
247
  <property name="currentIndex">
248
  <number>0</number>
249
  </property>
250
- <widget class="QWidget" name="tab_3">
251
  <attribute name="title">
252
  <string>Scaffold/Chromosome Viewer:</string>
253
  </attribute>
254
  <layout class="QGridLayout" name="gridLayout_4">
255
  <item row="0" column="0">
256
- <widget class="QGraphicsView" name="graphicsView_2">
257
  <property name="sizePolicy">
258
  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
259
  <horstretch>0</horstretch>
@@ -269,7 +213,7 @@
269
  </widget>
270
  </item>
271
  <item row="1" column="0">
272
- <widget class="QScrollArea" name="scrollArea">
273
  <property name="sizePolicy">
274
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
275
  <horstretch>0</horstretch>
@@ -279,13 +223,13 @@
279
  <property name="widgetResizable">
280
  <bool>true</bool>
281
  </property>
282
- <widget class="QWidget" name="scrollAreaWidgetContents">
283
  <property name="geometry">
284
  <rect>
285
  <x>0</x>
286
  <y>0</y>
287
- <width>424</width>
288
- <height>121</height>
289
  </rect>
290
  </property>
291
  </widget>
@@ -293,13 +237,13 @@
293
  </item>
294
  </layout>
295
  </widget>
296
- <widget class="QWidget" name="tab_4">
297
  <attribute name="title">
298
  <string>Seed Distribution</string>
299
  </attribute>
300
  <layout class="QGridLayout" name="gridLayout_9">
301
  <item row="0" column="0">
302
- <widget class="QWidget" name="repeats_vs_chromo" native="true"/>
303
  </item>
304
  </layout>
305
  </widget>
@@ -308,8 +252,8 @@
308
  </layout>
309
  </widget>
310
  </item>
311
- <item row="3" column="1">
312
- <widget class="QGroupBox" name="groupBox_3">
313
  <property name="sizePolicy">
314
  <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
315
  <horstretch>0</horstretch>
@@ -332,24 +276,8 @@
332
  <string>Global Analysis</string>
333
  </property>
334
  <layout class="QGridLayout" name="gridLayout_7">
335
- <item row="3" column="0">
336
- <spacer name="verticalSpacer_6">
337
- <property name="orientation">
338
- <enum>Qt::Vertical</enum>
339
- </property>
340
- <property name="sizeType">
341
- <enum>QSizePolicy::Fixed</enum>
342
- </property>
343
- <property name="sizeHint" stdset="0">
344
- <size>
345
- <width>10</width>
346
- <height>10</height>
347
- </size>
348
- </property>
349
- </spacer>
350
- </item>
351
- <item row="1" column="0">
352
- <widget class="QPushButton" name="statistics_overview">
353
  <property name="sizePolicy">
354
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
355
  <horstretch>0</horstretch>
@@ -367,8 +295,8 @@
367
  </property>
368
  </widget>
369
  </item>
370
- <item row="2" column="0">
371
- <widget class="QTabWidget" name="tabWidget_2">
372
  <property name="sizePolicy">
373
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
374
  <horstretch>0</horstretch>
@@ -378,23 +306,23 @@
378
  <property name="currentIndex">
379
  <number>1</number>
380
  </property>
381
- <widget class="QWidget" name="tab">
382
  <attribute name="title">
383
- <string>Repeats per ID Number</string>
384
  </attribute>
385
  <layout class="QGridLayout" name="gridLayout_11">
386
  <item row="0" column="0">
387
- <widget class="QWidget" name="repeats_vs_seeds_line" native="true"/>
388
  </item>
389
  </layout>
390
  </widget>
391
- <widget class="QWidget" name="tab_2">
392
  <attribute name="title">
393
- <string>Seeds per Number Repeats</string>
394
  </attribute>
395
  <layout class="QGridLayout" name="gridLayout_10">
396
  <item row="0" column="0">
397
- <widget class="QWidget" name="seeds_vs_repeats_bar" native="true">
398
  <property name="sizePolicy">
399
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
400
  <horstretch>0</horstretch>
@@ -407,41 +335,18 @@
407
  </widget>
408
  </widget>
409
  </item>
410
- <item row="0" column="0">
411
- <spacer name="verticalSpacer_5">
412
- <property name="orientation">
413
- <enum>Qt::Vertical</enum>
414
- </property>
415
- <property name="sizeType">
416
- <enum>QSizePolicy::Fixed</enum>
417
- </property>
418
- <property name="sizeHint" stdset="0">
419
- <size>
420
- <width>10</width>
421
- <height>10</height>
422
- </size>
423
- </property>
424
- </spacer>
425
- </item>
426
  </layout>
427
  </widget>
428
  </item>
429
  <item row="0" column="0">
430
- <widget class="QLabel" name="title">
431
  <property name="text">
432
  <string>Multitargeting</string>
433
  </property>
434
  </widget>
435
  </item>
436
- <item row="1" column="0" colspan="2">
437
- <widget class="Line" name="line">
438
- <property name="orientation">
439
- <enum>Qt::Horizontal</enum>
440
- </property>
441
- </widget>
442
- </item>
443
- <item row="4" column="0" alignment="Qt::AlignLeft">
444
- <widget class="QPushButton" name="back_button">
445
  <property name="sizePolicy">
446
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
447
  <horstretch>0</horstretch>
@@ -459,8 +364,8 @@
459
  </property>
460
  </widget>
461
  </item>
462
- <item row="4" column="1" alignment="Qt::AlignRight">
463
- <widget class="QPushButton" name="export_button">
464
  <property name="sizePolicy">
465
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
466
  <horstretch>0</horstretch>
@@ -480,73 +385,8 @@
480
  </item>
481
  </layout>
482
  </item>
483
- <item row="1" column="0">
484
- <spacer name="horizontalSpacer">
485
- <property name="orientation">
486
- <enum>Qt::Horizontal</enum>
487
- </property>
488
- <property name="sizeType">
489
- <enum>QSizePolicy::Fixed</enum>
490
- </property>
491
- <property name="sizeHint" stdset="0">
492
- <size>
493
- <width>20</width>
494
- <height>20</height>
495
- </size>
496
- </property>
497
- </spacer>
498
- </item>
499
- <item row="2" column="1">
500
- <spacer name="verticalSpacer">
501
- <property name="orientation">
502
- <enum>Qt::Vertical</enum>
503
- </property>
504
- <property name="sizeType">
505
- <enum>QSizePolicy::Fixed</enum>
506
- </property>
507
- <property name="sizeHint" stdset="0">
508
- <size>
509
- <width>20</width>
510
- <height>20</height>
511
- </size>
512
- </property>
513
- </spacer>
514
- </item>
515
- <item row="1" column="2">
516
- <spacer name="horizontalSpacer_2">
517
- <property name="orientation">
518
- <enum>Qt::Horizontal</enum>
519
- </property>
520
- <property name="sizeType">
521
- <enum>QSizePolicy::Fixed</enum>
522
- </property>
523
- <property name="sizeHint" stdset="0">
524
- <size>
525
- <width>20</width>
526
- <height>20</height>
527
- </size>
528
- </property>
529
- </spacer>
530
- </item>
531
- <item row="0" column="1">
532
- <spacer name="verticalSpacer_2">
533
- <property name="orientation">
534
- <enum>Qt::Vertical</enum>
535
- </property>
536
- <property name="sizeType">
537
- <enum>QSizePolicy::Fixed</enum>
538
- </property>
539
- <property name="sizeHint" stdset="0">
540
- <size>
541
- <width>20</width>
542
- <height>20</height>
543
- </size>
544
- </property>
545
- </spacer>
546
- </item>
547
  </layout>
548
  </widget>
549
- <widget class="QStatusBar" name="statusbar"/>
550
  </widget>
551
  <resources/>
552
  <connections/>
 
19
  <string>MainWindow</string>
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
+ <property name="geometry">
23
+ <rect>
24
+ <x>0</x>
25
+ <y>0</y>
26
+ <width>885</width>
27
+ <height>564</height>
28
+ </rect>
29
+ </property>
30
  <layout class="QGridLayout" name="gridLayout_2">
31
+ <item row="0" column="0">
32
  <layout class="QGridLayout" name="gridLayout">
33
+ <item row="1" column="0" rowspan="2">
34
+ <widget class="QGroupBox" name="grpSelectOrganism">
35
  <property name="sizePolicy">
36
  <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
37
  <horstretch>0</horstretch>
 
42
  <string>Select Organism and Endonuclease:</string>
43
  </property>
44
  <layout class="QGridLayout" name="gridLayout_8">
45
+ <item row="1" column="0">
46
+ <widget class="QComboBox" name="cmbOrganism">
47
  <property name="sizePolicy">
48
  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
49
  <horstretch>0</horstretch>
 
58
  </property>
59
  </widget>
60
  </item>
61
+ <item row="2" column="0" colspan="2">
62
  <layout class="QHBoxLayout" name="horizontalLayout">
63
  <item>
64
+ <widget class="QPushButton" name="pbtnAnalyze">
65
  <property name="sizePolicy">
66
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
67
  <horstretch>0</horstretch>
 
80
  </widget>
81
  </item>
82
  <item>
83
+ <widget class="QToolButton" name="tbtnSQLSettings">
84
  <property name="minimumSize">
85
  <size>
86
  <width>0</width>
 
94
  </item>
95
  </layout>
96
  </item>
97
+ <item row="0" column="1">
98
+ <widget class="QLabel" name="lblEndonuclease">
99
  <property name="sizePolicy">
100
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
101
  <horstretch>0</horstretch>
 
107
  </property>
108
  </widget>
109
  </item>
110
+ <item row="4" column="0" colspan="2">
111
+ <widget class="QTableWidget" name="tblSeeds">
112
  <property name="minimumSize">
113
  <size>
114
  <width>400</width>
 
117
  </property>
118
  </widget>
119
  </item>
120
+ <item row="0" column="0">
121
+ <widget class="QLabel" name="lblOrganism">
122
  <property name="sizePolicy">
123
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
124
  <horstretch>0</horstretch>
 
130
  </property>
131
  </widget>
132
  </item>
133
+ <item row="1" column="1">
134
+ <widget class="QComboBox" name="cmbEndonuclease">
135
  <property name="sizePolicy">
136
  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
137
  <horstretch>0</horstretch>
 
146
  </property>
147
  </widget>
148
  </item>
149
+ <item row="3" column="0">
150
+ <widget class="QCheckBox" name="chkSelectAll">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  <property name="text">
152
  <string>Select All</string>
153
  </property>
 
156
  </layout>
157
  </widget>
158
  </item>
159
+ <item row="1" column="1">
160
+ <widget class="QGroupBox" name="grpSeedAnalysis">
161
  <property name="sizePolicy">
162
  <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
163
  <horstretch>0</horstretch>
 
174
  <string>Seed Analysis</string>
175
  </property>
176
  <layout class="QGridLayout" name="gridLayout_6">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  <item row="0" column="0">
178
+ <widget class="QTabWidget" name="tabsSeedAnalysis">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  <property name="sizePolicy">
180
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
181
  <horstretch>0</horstretch>
 
191
  <property name="currentIndex">
192
  <number>0</number>
193
  </property>
194
+ <widget class="QWidget" name="tabChromosomeViewer">
195
  <attribute name="title">
196
  <string>Scaffold/Chromosome Viewer:</string>
197
  </attribute>
198
  <layout class="QGridLayout" name="gridLayout_4">
199
  <item row="0" column="0">
200
+ <widget class="QGraphicsView" name="graphviewChromosome">
201
  <property name="sizePolicy">
202
  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
203
  <horstretch>0</horstretch>
 
213
  </widget>
214
  </item>
215
  <item row="1" column="0">
216
+ <widget class="QScrollArea" name="scrollChromosome">
217
  <property name="sizePolicy">
218
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
219
  <horstretch>0</horstretch>
 
223
  <property name="widgetResizable">
224
  <bool>true</bool>
225
  </property>
226
+ <widget class="QWidget" name="scrollviewChromosome">
227
  <property name="geometry">
228
  <rect>
229
  <x>0</x>
230
  <y>0</y>
231
+ <width>369</width>
232
+ <height>112</height>
233
  </rect>
234
  </property>
235
  </widget>
 
237
  </item>
238
  </layout>
239
  </widget>
240
+ <widget class="QWidget" name="tabSeedDistribution">
241
  <attribute name="title">
242
  <string>Seed Distribution</string>
243
  </attribute>
244
  <layout class="QGridLayout" name="gridLayout_9">
245
  <item row="0" column="0">
246
+ <widget class="QWidget" name="plotRepeatVsChromosome" native="true"/>
247
  </item>
248
  </layout>
249
  </widget>
 
252
  </layout>
253
  </widget>
254
  </item>
255
+ <item row="2" column="1">
256
+ <widget class="QGroupBox" name="grpGlobalAnalysis">
257
  <property name="sizePolicy">
258
  <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
259
  <horstretch>0</horstretch>
 
276
  <string>Global Analysis</string>
277
  </property>
278
  <layout class="QGridLayout" name="gridLayout_7">
279
+ <item row="0" column="0">
280
+ <widget class="QPushButton" name="pbtnStatisticsOverview">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  <property name="sizePolicy">
282
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
283
  <horstretch>0</horstretch>
 
295
  </property>
296
  </widget>
297
  </item>
298
+ <item row="1" column="0">
299
+ <widget class="QTabWidget" name="tabsGlobalAnalysis">
300
  <property name="sizePolicy">
301
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
302
  <horstretch>0</horstretch>
 
306
  <property name="currentIndex">
307
  <number>1</number>
308
  </property>
309
+ <widget class="QWidget" name="tabRepeatsVsSeed">
310
  <attribute name="title">
311
+ <string>Repeats per Seed ID Number</string>
312
  </attribute>
313
  <layout class="QGridLayout" name="gridLayout_11">
314
  <item row="0" column="0">
315
+ <widget class="QWidget" name="plotRepeatsVsSeed" native="true"/>
316
  </item>
317
  </layout>
318
  </widget>
319
+ <widget class="QWidget" name="tabSequencesVsRepeats">
320
  <attribute name="title">
321
+ <string>Sequences per Number Repeats</string>
322
  </attribute>
323
  <layout class="QGridLayout" name="gridLayout_10">
324
  <item row="0" column="0">
325
+ <widget class="QWidget" name="plotSequencesVsRepeats" native="true">
326
  <property name="sizePolicy">
327
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
328
  <horstretch>0</horstretch>
 
335
  </widget>
336
  </widget>
337
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  </layout>
339
  </widget>
340
  </item>
341
  <item row="0" column="0">
342
+ <widget class="QLabel" name="lblMultitargeting">
343
  <property name="text">
344
  <string>Multitargeting</string>
345
  </property>
346
  </widget>
347
  </item>
348
+ <item row="3" column="0" alignment="Qt::AlignLeft">
349
+ <widget class="QPushButton" name="pbtnBank">
 
 
 
 
 
 
 
350
  <property name="sizePolicy">
351
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
352
  <horstretch>0</horstretch>
 
364
  </property>
365
  </widget>
366
  </item>
367
+ <item row="3" column="1" alignment="Qt::AlignRight">
368
+ <widget class="QPushButton" name="pbtnExport">
369
  <property name="sizePolicy">
370
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
371
  <horstretch>0</horstretch>
 
385
  </item>
386
  </layout>
387
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  </layout>
389
  </widget>
 
390
  </widget>
391
  <resources/>
392
  <connections/>
src/ui/ncbi_window_v2.ui CHANGED
@@ -21,16 +21,6 @@
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
  <item row="0" column="0">
24
- <widget class="QLabel" name="lblRequired">
25
- <property name="text">
26
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;* Required&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
27
- </property>
28
- <property name="alignment">
29
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
30
- </property>
31
- </widget>
32
- </item>
33
- <item row="1" column="0">
34
  <layout class="QGridLayout" name="gridLayout">
35
  <item row="2" column="0" colspan="2">
36
  <widget class="QGroupBox" name="grpStep2">
 
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
  <item row="0" column="0">
 
 
 
 
 
 
 
 
 
 
24
  <layout class="QGridLayout" name="gridLayout">
25
  <item row="2" column="0" colspan="2">
26
  <widget class="QGroupBox" name="grpStep2">
src/ui/{pop.ui → population_analysis.ui} RENAMED
@@ -20,88 +20,27 @@
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="0" column="1">
24
- <spacer name="verticalSpacer">
25
- <property name="orientation">
26
- <enum>Qt::Vertical</enum>
27
- </property>
28
- <property name="sizeType">
29
- <enum>QSizePolicy::Fixed</enum>
30
- </property>
31
- <property name="sizeHint" stdset="0">
32
- <size>
33
- <width>20</width>
34
- <height>20</height>
35
- </size>
36
- </property>
37
- </spacer>
38
- </item>
39
- <item row="1" column="1">
40
  <layout class="QGridLayout" name="gridLayout">
41
- <item row="2" column="0" rowspan="2">
42
- <widget class="QGroupBox" name="groupBox">
43
  <property name="sizePolicy">
44
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
45
  <horstretch>0</horstretch>
46
  <verstretch>0</verstretch>
47
  </sizepolicy>
48
  </property>
49
- <property name="maximumSize">
50
- <size>
51
- <width>600</width>
52
- <height>16777215</height>
53
- </size>
54
- </property>
55
  <property name="toolTip">
56
- <string>Please select organism(s) and an endonuclease, and click &quot;Analyze Organism(s)&quot; to populate the population analysis window.</string>
57
  </property>
58
  <property name="title">
59
- <string>Select Organism(s) and Endonuclease:</string>
60
  </property>
61
- <layout class="QGridLayout" name="gridLayout_6">
62
- <item row="3" column="0" colspan="2">
63
- <widget class="QPushButton" name="analyze_button">
64
- <property name="font">
65
- <font>
66
- <weight>75</weight>
67
- <bold>true</bold>
68
- </font>
69
- </property>
70
- <property name="text">
71
- <string>Analyze Organism(s)</string>
72
- </property>
73
- </widget>
74
- </item>
75
- <item row="0" column="0" colspan="2">
76
- <spacer name="verticalSpacer_3">
77
- <property name="orientation">
78
- <enum>Qt::Vertical</enum>
79
- </property>
80
- <property name="sizeType">
81
- <enum>QSizePolicy::Fixed</enum>
82
- </property>
83
- <property name="sizeHint" stdset="0">
84
- <size>
85
- <width>10</width>
86
- <height>10</height>
87
- </size>
88
- </property>
89
- </spacer>
90
- </item>
91
- <item row="1" column="1">
92
- <widget class="QComboBox" name="endoBox">
93
- <property name="sizePolicy">
94
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
95
- <horstretch>0</horstretch>
96
- <verstretch>0</verstretch>
97
- </sizepolicy>
98
- </property>
99
- </widget>
100
- </item>
101
- <item row="2" column="0" colspan="2">
102
- <widget class="QTableWidget" name="org_Table">
103
  <property name="sizePolicy">
104
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
105
  <horstretch>0</horstretch>
106
  <verstretch>0</verstretch>
107
  </sizepolicy>
@@ -114,23 +53,47 @@
114
  </property>
115
  </widget>
116
  </item>
117
- <item row="1" column="0">
118
- <widget class="QLabel" name="label">
119
  <property name="sizePolicy">
120
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
121
  <horstretch>0</horstretch>
122
  <verstretch>0</verstretch>
123
  </sizepolicy>
124
  </property>
125
  <property name="text">
126
- <string>Endonuclease:</string>
 
 
 
 
 
 
 
127
  </property>
128
  </widget>
129
  </item>
130
- <item row="4" column="0" colspan="2">
131
- <widget class="QTabWidget" name="tabWidget">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  <property name="sizePolicy">
133
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
134
  <horstretch>0</horstretch>
135
  <verstretch>0</verstretch>
136
  </sizepolicy>
@@ -142,88 +105,84 @@
142
  </size>
143
  </property>
144
  <property name="toolTip">
145
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Heatmap of shared repeats between all selected organisms. Diagonals show number of self-contained repeats. Axis labels correspond to the rows of the organism selection table.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
146
- </property>
147
- <property name="currentIndex">
148
- <number>0</number>
149
  </property>
150
- <widget class="QWidget" name="tab">
151
- <attribute name="title">
152
- <string>Shared Seed Heatmap</string>
153
- </attribute>
154
- <layout class="QGridLayout" name="gridLayout_3">
155
- <item row="0" column="0">
156
- <widget class="QWidget" name="colormap_figure" native="true"/>
157
- </item>
158
- </layout>
159
- </widget>
160
  </widget>
161
  </item>
162
- <item row="5" column="0" colspan="2">
163
- <spacer name="verticalSpacer_4">
164
- <property name="orientation">
165
- <enum>Qt::Vertical</enum>
166
  </property>
167
- <property name="sizeType">
168
- <enum>QSizePolicy::Fixed</enum>
169
  </property>
170
- <property name="sizeHint" stdset="0">
171
- <size>
172
- <width>10</width>
173
- <height>10</height>
174
- </size>
 
175
  </property>
176
- </spacer>
177
  </item>
178
  </layout>
179
  </widget>
180
  </item>
181
- <item row="4" column="0" alignment="Qt::AlignLeft">
182
- <widget class="QPushButton" name="goBackButton">
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  <property name="minimumSize">
184
  <size>
185
- <width>125</width>
186
  <height>0</height>
187
  </size>
188
  </property>
 
 
 
189
  <property name="text">
190
- <string>Back</string>
191
  </property>
192
  </widget>
193
  </item>
194
- <item row="2" column="1" rowspan="2">
195
- <widget class="QGroupBox" name="groupBox_2">
196
  <property name="sizePolicy">
197
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
198
  <horstretch>0</horstretch>
199
  <verstretch>0</verstretch>
200
  </sizepolicy>
201
  </property>
 
 
 
 
 
 
202
  <property name="toolTip">
203
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;These tables show the seeds that are conserved among &lt;span style=&quot; font-weight:600;&quot;&gt;all &lt;/span&gt;selected organisms.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
204
  </property>
205
  <property name="title">
206
- <string>Seed Analysis:</string>
207
  </property>
208
- <layout class="QGridLayout" name="gridLayout_5">
209
- <item row="4" column="1">
210
- <widget class="QPushButton" name="find_locs_button">
211
- <property name="text">
212
- <string>Find Locations</string>
213
- </property>
214
- </widget>
215
- </item>
216
- <item row="2" column="0">
217
- <widget class="QPushButton" name="query_seed_button">
218
- <property name="text">
219
- <string>Query Seed</string>
220
- </property>
221
- </widget>
222
- </item>
223
- <item row="3" column="0" colspan="3">
224
- <widget class="QTableWidget" name="table2">
225
  <property name="sizePolicy">
226
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
227
  <horstretch>0</horstretch>
228
  <verstretch>0</verstretch>
229
  </sizepolicy>
@@ -235,57 +194,40 @@
235
  </size>
236
  </property>
237
  <property name="toolTip">
238
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;% Coverage&lt;/span&gt;: percentage of analyzed organisms covered by the given seed sequence. (Ex. a coverage of 75% for an analysis of 4 organisms means that the seed appears in 3/4 of the organisms) &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Total Repeats&lt;/span&gt;: the total number of times the seed sequence is repeated across all organisms analyzed &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Avg. Repeats/Scaffold&lt;/span&gt;: the average number of times the seed sequence is repeated per scaffold/chromosome/contig (varies depending on how the organism’s FASTA file is arranged) &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Consensus Sequence&lt;/span&gt;: the full length guide RNA sequence that appears most commonly amongst all occurrences of the given seed. &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;% Consensus&lt;/span&gt;: the percentage of all seed repeats that have the tail of the consensus sequence. &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Score&lt;/span&gt;: on-target score for the consensus sequence.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;PAM&lt;/span&gt;: the protospacer adjacent motif of the consensus sequence.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Strand&lt;/span&gt;: the strand directionality (+ or -) of the consensus sequence.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
239
- </property>
240
- </widget>
241
- </item>
242
- <item row="2" column="2">
243
- <widget class="QPushButton" name="clear_Button">
244
- <property name="text">
245
- <string>Clear Seeds</string>
246
- </property>
247
- </widget>
248
- </item>
249
- <item row="1" column="1" colspan="2">
250
- <widget class="QLineEdit" name="seed_input"/>
251
- </item>
252
- <item row="0" column="0" colspan="3">
253
- <spacer name="verticalSpacer_5">
254
- <property name="orientation">
255
- <enum>Qt::Vertical</enum>
256
- </property>
257
- <property name="sizeType">
258
- <enum>QSizePolicy::Fixed</enum>
259
- </property>
260
- <property name="sizeHint" stdset="0">
261
- <size>
262
- <width>10</width>
263
- <height>10</height>
264
- </size>
265
  </property>
266
- </spacer>
267
- </item>
268
- <item row="4" column="2">
269
- <widget class="QPushButton" name="clear_loc_button">
270
- <property name="text">
271
- <string>Clear Locations</string>
272
  </property>
 
 
 
 
 
 
 
 
 
 
273
  </widget>
274
  </item>
275
- <item row="4" column="0">
276
- <widget class="QLabel" name="label_3">
277
- <property name="toolTip">
278
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Highlight a seed and click &amp;quot;Find Locations&amp;quot; to view the exact location and organism of every repeat belonging to that seed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
 
 
279
  </property>
280
  <property name="text">
281
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Location Finder:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
282
  </property>
283
  </widget>
284
  </item>
285
- <item row="5" column="0" colspan="3">
286
- <widget class="QTableWidget" name="loc_finder_table">
287
  <property name="sizePolicy">
288
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
289
  <horstretch>0</horstretch>
290
  <verstretch>0</verstretch>
291
  </sizepolicy>
@@ -298,127 +240,49 @@
298
  </property>
299
  </widget>
300
  </item>
301
- <item row="1" column="0">
302
- <widget class="QLabel" name="label_2">
303
- <property name="sizePolicy">
304
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
305
- <horstretch>0</horstretch>
306
- <verstretch>0</verstretch>
307
- </sizepolicy>
308
  </property>
309
  <property name="text">
310
- <string>Seed Search:</string>
311
  </property>
312
  </widget>
313
  </item>
314
- <item row="6" column="0" colspan="3">
315
- <spacer name="verticalSpacer_6">
316
- <property name="orientation">
317
- <enum>Qt::Vertical</enum>
318
- </property>
319
- <property name="sizeType">
320
- <enum>QSizePolicy::Fixed</enum>
321
- </property>
322
- <property name="sizeHint" stdset="0">
323
- <size>
324
- <width>10</width>
325
- <height>10</height>
326
- </size>
327
  </property>
328
- </spacer>
329
  </item>
330
  </layout>
331
  </widget>
332
  </item>
333
- <item row="4" column="1" alignment="Qt::AlignRight">
334
- <widget class="QPushButton" name="export_button">
335
  <property name="minimumSize">
336
  <size>
337
- <width>175</width>
338
  <height>0</height>
339
  </size>
340
  </property>
341
- <property name="toolTip">
342
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Click to export selected gRNAs. &lt;span style=&quot; font-weight:600;&quot;&gt;Note: &lt;/span&gt;only rows from the first (top right) table can be selected and exported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
343
- </property>
344
- <property name="text">
345
- <string>Export Selected gRNAs</string>
346
- </property>
347
- </widget>
348
- </item>
349
- <item row="0" column="0" colspan="2">
350
- <widget class="QLabel" name="title">
351
- <property name="sizePolicy">
352
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
353
- <horstretch>0</horstretch>
354
- <verstretch>0</verstretch>
355
- </sizepolicy>
356
- </property>
357
  <property name="text">
358
- <string>Population Analysis</string>
359
- </property>
360
- </widget>
361
- </item>
362
- <item row="1" column="0" colspan="2">
363
- <widget class="Line" name="line">
364
- <property name="orientation">
365
- <enum>Qt::Horizontal</enum>
366
  </property>
367
  </widget>
368
  </item>
369
  </layout>
370
  </item>
371
- <item row="2" column="1">
372
- <spacer name="verticalSpacer_2">
373
- <property name="orientation">
374
- <enum>Qt::Vertical</enum>
375
- </property>
376
- <property name="sizeType">
377
- <enum>QSizePolicy::Fixed</enum>
378
- </property>
379
- <property name="sizeHint" stdset="0">
380
- <size>
381
- <width>20</width>
382
- <height>20</height>
383
- </size>
384
- </property>
385
- </spacer>
386
- </item>
387
- <item row="1" column="2">
388
- <spacer name="horizontalSpacer">
389
- <property name="orientation">
390
- <enum>Qt::Horizontal</enum>
391
- </property>
392
- <property name="sizeType">
393
- <enum>QSizePolicy::Fixed</enum>
394
- </property>
395
- <property name="sizeHint" stdset="0">
396
- <size>
397
- <width>20</width>
398
- <height>20</height>
399
- </size>
400
- </property>
401
- </spacer>
402
- </item>
403
- <item row="1" column="0">
404
- <spacer name="horizontalSpacer_2">
405
- <property name="orientation">
406
- <enum>Qt::Horizontal</enum>
407
- </property>
408
- <property name="sizeType">
409
- <enum>QSizePolicy::Fixed</enum>
410
- </property>
411
- <property name="sizeHint" stdset="0">
412
- <size>
413
- <width>20</width>
414
- <height>20</height>
415
- </size>
416
- </property>
417
- </spacer>
418
- </item>
419
  </layout>
420
  </widget>
421
- <widget class="QStatusBar" name="statusbar"/>
422
  </widget>
423
  <resources/>
424
  <connections/>
 
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
+ <item row="0" column="0">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  <layout class="QGridLayout" name="gridLayout">
25
+ <item row="1" column="1" rowspan="2">
26
+ <widget class="QGroupBox" name="grpSeedAnalysis">
27
  <property name="sizePolicy">
28
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
29
  <horstretch>0</horstretch>
30
  <verstretch>0</verstretch>
31
  </sizepolicy>
32
  </property>
 
 
 
 
 
 
33
  <property name="toolTip">
34
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;These tables show the seeds that are conserved among &lt;span style=&quot; font-weight:600;&quot;&gt;all &lt;/span&gt;selected organisms.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
35
  </property>
36
  <property name="title">
37
+ <string>Seed Analysis:</string>
38
  </property>
39
+ <layout class="QGridLayout" name="gridLayout_5">
40
+ <item row="4" column="0" colspan="3">
41
+ <widget class="QTableWidget" name="tblLocation">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  <property name="sizePolicy">
43
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
44
  <horstretch>0</horstretch>
45
  <verstretch>0</verstretch>
46
  </sizepolicy>
 
53
  </property>
54
  </widget>
55
  </item>
56
+ <item row="0" column="0">
57
+ <widget class="QLabel" name="lblSeedSearch">
58
  <property name="sizePolicy">
59
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
60
  <horstretch>0</horstretch>
61
  <verstretch>0</verstretch>
62
  </sizepolicy>
63
  </property>
64
  <property name="text">
65
+ <string>Seed Search:</string>
66
+ </property>
67
+ </widget>
68
+ </item>
69
+ <item row="1" column="0">
70
+ <widget class="QPushButton" name="pbtnQuerySeed">
71
+ <property name="text">
72
+ <string>Query Seed</string>
73
  </property>
74
  </widget>
75
  </item>
76
+ <item row="3" column="2">
77
+ <widget class="QPushButton" name="pbtnClearLocations">
78
+ <property name="text">
79
+ <string>Clear Locations</string>
80
+ </property>
81
+ </widget>
82
+ </item>
83
+ <item row="0" column="1" colspan="2">
84
+ <widget class="QLineEdit" name="ledSeed"/>
85
+ </item>
86
+ <item row="1" column="2">
87
+ <widget class="QPushButton" name="pbtnClearSeeds">
88
+ <property name="text">
89
+ <string>Clear Seeds</string>
90
+ </property>
91
+ </widget>
92
+ </item>
93
+ <item row="2" column="0" colspan="3">
94
+ <widget class="QTableWidget" name="tblSeed">
95
  <property name="sizePolicy">
96
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
97
  <horstretch>0</horstretch>
98
  <verstretch>0</verstretch>
99
  </sizepolicy>
 
105
  </size>
106
  </property>
107
  <property name="toolTip">
108
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;% Coverage&lt;/span&gt;: percentage of analyzed organisms covered by the given seed sequence. (Ex. a coverage of 75% for an analysis of 4 organisms means that the seed appears in 3/4 of the organisms) &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Total Repeats&lt;/span&gt;: the total number of times the seed sequence is repeated across all organisms analyzed &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Avg. Repeats/Scaffold&lt;/span&gt;: the average number of times the seed sequence is repeated per scaffold/chromosome/contig (varies depending on how the organism’s FASTA file is arranged) &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Consensus Sequence&lt;/span&gt;: the full length guide RNA sequence that appears most commonly amongst all occurrences of the given seed. &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;% Consensus&lt;/span&gt;: the percentage of all seed repeats that have the tail of the consensus sequence. &lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Score&lt;/span&gt;: on-target score for the consensus sequence.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;PAM&lt;/span&gt;: the protospacer adjacent motif of the consensus sequence.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Strand&lt;/span&gt;: the strand directionality (+ or -) of the consensus sequence.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
 
 
109
  </property>
 
 
 
 
 
 
 
 
 
 
110
  </widget>
111
  </item>
112
+ <item row="3" column="0">
113
+ <widget class="QLabel" name="lblLocationFInder">
114
+ <property name="toolTip">
115
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Highlight a seed and click &amp;quot;Find Locations&amp;quot; to view the exact location and organism of every repeat belonging to that seed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
116
  </property>
117
+ <property name="text">
118
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Location Finder:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
119
  </property>
120
+ </widget>
121
+ </item>
122
+ <item row="3" column="1">
123
+ <widget class="QPushButton" name="pbtnFindLocations">
124
+ <property name="text">
125
+ <string>Find Locations</string>
126
  </property>
127
+ </widget>
128
  </item>
129
  </layout>
130
  </widget>
131
  </item>
132
+ <item row="0" column="0" colspan="2">
133
+ <widget class="QLabel" name="lblPopulationAnalysis">
134
+ <property name="sizePolicy">
135
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
136
+ <horstretch>0</horstretch>
137
+ <verstretch>0</verstretch>
138
+ </sizepolicy>
139
+ </property>
140
+ <property name="text">
141
+ <string>Population Analysis</string>
142
+ </property>
143
+ </widget>
144
+ </item>
145
+ <item row="3" column="1" alignment="Qt::AlignRight">
146
+ <widget class="QPushButton" name="pbtnExportgRNA">
147
  <property name="minimumSize">
148
  <size>
149
+ <width>175</width>
150
  <height>0</height>
151
  </size>
152
  </property>
153
+ <property name="toolTip">
154
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Click to export selected gRNAs. &lt;span style=&quot; font-weight:600;&quot;&gt;Note: &lt;/span&gt;only rows from the first (top right) table can be selected and exported.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
155
+ </property>
156
  <property name="text">
157
+ <string>Export Selected gRNAs</string>
158
  </property>
159
  </widget>
160
  </item>
161
+ <item row="1" column="0" rowspan="2">
162
+ <widget class="QGroupBox" name="grpSelectOrganisms">
163
  <property name="sizePolicy">
164
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
165
  <horstretch>0</horstretch>
166
  <verstretch>0</verstretch>
167
  </sizepolicy>
168
  </property>
169
+ <property name="maximumSize">
170
+ <size>
171
+ <width>600</width>
172
+ <height>16777215</height>
173
+ </size>
174
+ </property>
175
  <property name="toolTip">
176
+ <string>Please select organism(s) and an endonuclease, and click &quot;Analyze Organism(s)&quot; to populate the population analysis window.</string>
177
  </property>
178
  <property name="title">
179
+ <string>Select Organism(s) and Endonuclease:</string>
180
  </property>
181
+ <layout class="QGridLayout" name="gridLayout_6">
182
+ <item row="3" column="0" colspan="2">
183
+ <widget class="QTabWidget" name="tabsSharedSeedHeatmap">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  <property name="sizePolicy">
185
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
186
  <horstretch>0</horstretch>
187
  <verstretch>0</verstretch>
188
  </sizepolicy>
 
194
  </size>
195
  </property>
196
  <property name="toolTip">
197
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Heatmap of shared repeats between all selected organisms. Diagonals show number of self-contained repeats. Axis labels correspond to the rows of the organism selection table.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  </property>
199
+ <property name="currentIndex">
200
+ <number>0</number>
 
 
 
 
201
  </property>
202
+ <widget class="QWidget" name="tabSharedSeedHeatmap">
203
+ <attribute name="title">
204
+ <string>Shared Seed Heatmap</string>
205
+ </attribute>
206
+ <layout class="QGridLayout" name="gridLayout_3">
207
+ <item row="0" column="0">
208
+ <widget class="QWidget" name="heatmapSeed" native="true"/>
209
+ </item>
210
+ </layout>
211
+ </widget>
212
  </widget>
213
  </item>
214
+ <item row="0" column="0">
215
+ <widget class="QLabel" name="lblEndonuclease">
216
+ <property name="sizePolicy">
217
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
218
+ <horstretch>0</horstretch>
219
+ <verstretch>0</verstretch>
220
+ </sizepolicy>
221
  </property>
222
  <property name="text">
223
+ <string>Endonuclease:</string>
224
  </property>
225
  </widget>
226
  </item>
227
+ <item row="1" column="0" colspan="2">
228
+ <widget class="QTableWidget" name="tblOrganism">
229
  <property name="sizePolicy">
230
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
231
  <horstretch>0</horstretch>
232
  <verstretch>0</verstretch>
233
  </sizepolicy>
 
240
  </property>
241
  </widget>
242
  </item>
243
+ <item row="2" column="0" colspan="2">
244
+ <widget class="QPushButton" name="pbtnAnalyzeOrganism">
245
+ <property name="font">
246
+ <font>
247
+ <weight>75</weight>
248
+ <bold>true</bold>
249
+ </font>
250
  </property>
251
  <property name="text">
252
+ <string>Analyze Organism(s)</string>
253
  </property>
254
  </widget>
255
  </item>
256
+ <item row="0" column="1">
257
+ <widget class="QComboBox" name="cmbEndonuclease">
258
+ <property name="sizePolicy">
259
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
260
+ <horstretch>0</horstretch>
261
+ <verstretch>0</verstretch>
262
+ </sizepolicy>
 
 
 
 
 
 
263
  </property>
264
+ </widget>
265
  </item>
266
  </layout>
267
  </widget>
268
  </item>
269
+ <item row="3" column="0" alignment="Qt::AlignLeft">
270
+ <widget class="QPushButton" name="pbtnBack">
271
  <property name="minimumSize">
272
  <size>
273
+ <width>125</width>
274
  <height>0</height>
275
  </size>
276
  </property>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  <property name="text">
278
+ <string>Back</string>
 
 
 
 
 
 
 
279
  </property>
280
  </widget>
281
  </item>
282
  </layout>
283
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  </layout>
285
  </widget>
 
286
  </widget>
287
  <resources/>
288
  <connections/>
src/utils/ui.py CHANGED
@@ -35,13 +35,8 @@ def show_error(settings, message, e):
35
 
36
  def scale_ui(window, base_width=1920, base_height=1080, font_size=12, header_font_size=30, custom_scale_width=None, custom_scale_height=None):
37
  try:
38
- window.repaint()
39
- QtWidgets.QApplication.processEvents()
40
-
41
  # Get the primary screen
42
  screen = QtGui.QGuiApplication.primaryScreen()
43
-
44
- # Get the geometry of the screen
45
  screen_geometry = screen.geometry()
46
  width = screen_geometry.width()
47
  height = screen_geometry.height()
@@ -53,31 +48,25 @@ def scale_ui(window, base_width=1920, base_height=1080, font_size=12, header_fon
53
  scaled_title_font_size = int(header_font_size * (width / base_width))
54
  window.title.setStyleSheet(f"font: bold {scaled_title_font_size}pt 'Arial';")
55
 
56
- window.adjustSize()
 
 
57
 
 
 
58
  currentWidth = window.size().width()
59
  currentHeight = window.size().height()
60
 
61
- # Window resize and center
62
- scaledWidth = int((width * (custom_scale_width if custom_scale_width else 1150)) / base_width)
63
- scaledHeight = int((height * (custom_scale_height if custom_scale_height else 650)) / base_height)
64
-
65
  if scaledHeight < currentHeight:
66
  scaledHeight = currentHeight
67
  if scaledWidth < currentWidth:
68
  scaledWidth = currentWidth
69
 
70
- # Center the window on the screen
71
- centerPoint = screen_geometry.center()
72
- x = centerPoint.x() - (scaledWidth // 2)
73
- y = centerPoint.y() - (scaledHeight // 2)
74
- window.setGeometry(x, y, scaledWidth, scaledHeight)
75
-
76
- window.repaint()
77
- QtWidgets.QApplication.processEvents()
78
 
79
  except Exception as e:
80
- print(f"Error in scale_ui: {e}")
81
 
82
  def center_ui(window):
83
  try:
 
35
 
36
  def scale_ui(window, base_width=1920, base_height=1080, font_size=12, header_font_size=30, custom_scale_width=None, custom_scale_height=None):
37
  try:
 
 
 
38
  # Get the primary screen
39
  screen = QtGui.QGuiApplication.primaryScreen()
 
 
40
  screen_geometry = screen.geometry()
41
  width = screen_geometry.width()
42
  height = screen_geometry.height()
 
48
  scaled_title_font_size = int(header_font_size * (width / base_width))
49
  window.title.setStyleSheet(f"font: bold {scaled_title_font_size}pt 'Arial';")
50
 
51
+ # Calculate sizes
52
+ scaledWidth = int((width * (custom_scale_width if custom_scale_width else 1150)) / base_width)
53
+ scaledHeight = int((height * (custom_scale_height if custom_scale_height else 650)) / base_height)
54
 
55
+ # Ensure minimum size
56
+ window.adjustSize()
57
  currentWidth = window.size().width()
58
  currentHeight = window.size().height()
59
 
 
 
 
 
60
  if scaledHeight < currentHeight:
61
  scaledHeight = currentHeight
62
  if scaledWidth < currentWidth:
63
  scaledWidth = currentWidth
64
 
65
+ # Resize in a single operation
66
+ window.resize(scaledWidth, scaledHeight)
 
 
 
 
 
 
67
 
68
  except Exception as e:
69
+ print(f"Error in scale_ui: {e}")
70
 
71
  def center_ui(window):
72
  try:
src/views/AnnotationParser.py DELETED
@@ -1,429 +0,0 @@
1
- ###############################################################################
2
- # INPUTS: inputs are the annotation files to parse. Currently, only gbff is supported.
3
- # OUTPUTS: the outputs are data structures that store the parsed data
4
- ################################################################################
5
-
6
- from PyQt5 import QtWidgets
7
- import gffutils
8
- import models.GlobalSettings as GlobalSettings
9
- import os
10
- from Bio import SeqIO
11
- import traceback
12
-
13
- logger = GlobalSettings.logger
14
-
15
- class Annotation_Parser:
16
- def __init__(self):
17
- try:
18
- #variables to use
19
- self.annotationFileName = "" #this is the variable that holds the filename itself
20
- self.txtLocusTag = False
21
- self.isGff = False
22
- self.isTxt = False
23
- self.max_chrom = 0
24
-
25
- #dictionary used for finding the genes in a txt annotation file
26
- #key: locus_tag
27
- #value: List of lists
28
- # essentially its all based on locus tag. So the key is the locus tag, and its data is:
29
- # [genomic accession, int, start, end, +\-]
30
- self.reg_dict = dict()
31
-
32
- #parallel dictionary used for the txt annotaion file
33
- #key: name + symbol (space in between each word)
34
- #value: locus_tag (indexes dict)
35
- self.para_dict = dict()
36
-
37
- #list of tuples containing (chromosome/scaffold # {int}, Feature matching search criteria {SeqFeature Object})
38
- self.results_list = list()
39
-
40
- except Exception as e:
41
- logger.critical("Error initializing Annotation_Parser class.")
42
- logger.critical(e)
43
- logger.critical(traceback.format_exc())
44
- msgBox = QtWidgets.QMessageBox()
45
- msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
46
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
47
- msgBox.setWindowTitle("Fatal Error")
48
- msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
49
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
50
- msgBox.exec()
51
-
52
- exit(-1)
53
-
54
- ### This function takes a list of lists and flattens it into a single list. Useful when dealing with a list of lists where the nested lists only have 1 entry.
55
- def flatten_list(self,t):
56
- return [item.lower() for sublist in t for item in sublist]
57
-
58
- ### This function finds how many chromosomes are within the selcted annotation file and returns the value
59
- def get_max_chrom(self):
60
- parser = SeqIO.parse(self.annotationFileName, 'genbank') # Initialize parser (iterator) for each query
61
- for i, record in enumerate(parser):
62
- max_chrom = i+1
63
- return max_chrom
64
-
65
- def get_sequence_info(self, query):
66
- try:
67
- self.results_list.clear()
68
- parser = SeqIO.parse(self.annotationFileName, 'genbank') # Initialize parser (iterator) for each query
69
- for j,record in enumerate(parser): # Each record corresponds to a chromosome/scaffold in the FNA/FASTA file
70
- tmp = str(record.seq).find(query)
71
- if tmp != -1: # If match is found
72
- return (j+1,tmp+1,tmp+len(query)) # Chromosome number, start index, stop index
73
- else:
74
- tmp = str(record.seq.reverse_complement()).find(query) # Check the reverse complement now
75
- if tmp != -1: # If match is found
76
- return (j+1,tmp-len(query),tmp-1) # Chromosome number, start index, stop index
77
- else:
78
- continue
79
- return False
80
-
81
- except Exception as e:
82
- logger.critical("Error in get_sequence_info() in annotation parser.")
83
- logger.critical(e)
84
- logger.critical(traceback.format_exc())
85
- msgBox = QtWidgets.QMessageBox()
86
- msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
87
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
88
- msgBox.setWindowTitle("Fatal Error")
89
- msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
90
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
91
- msgBox.exec()
92
-
93
- exit(-1)
94
-
95
- ### The workhorse function of AnnotationParser, this searches the annotation file for the user's search and returns features matching the description.
96
- def genbank_search(self, queries, same_search):
97
- index_number = 0
98
- try:
99
- if same_search: # If searching for the same thing, just return the results from last time
100
- return self.results_list
101
- else:
102
- self.results_list.clear()
103
- for i, query in enumerate(queries):
104
- parser = SeqIO.parse(self.annotationFileName, 'genbank') # Initialize parser (iterator) for each query
105
- for j,record in enumerate(parser): # Each record corresponds to a chromosome/scaffold in the FNA/FASTA file
106
- if i == 0:
107
- index_number += 1
108
- for feature in record.features: # Each feature corresponds to a gene, tRNA, rep_origin, etc. in the given record (chromosome/scaffold)
109
- if "translation" in feature.qualifiers:
110
- if query.lower() in " ".join(self.flatten_list(feature.qualifiers.values())[:-1]) and feature.type != "source" and feature.type != "gene": # If search matches the feature's qualifiers somewhere, save it
111
- self.results_list.append((j+1,feature))
112
- else: # If search not in the feature's qualifiers, move to the next feature
113
- continue
114
- else:
115
- if query.lower() in " ".join(self.flatten_list(feature.qualifiers.values())) and feature.type != "source" and feature.type != "gene": # If search matches the feature's qualifiers somewhere, save it
116
- self.results_list.append((j+1,feature))
117
- else: # If search not in the feature's qualifiers, move to the next feature
118
- continue
119
- self.max_chrom = index_number # Counts the number of chromosomes/scaffolds in the organism (only do this once, even if there are multiple queries)
120
- else:
121
- for feature in record.features:
122
- if "translation" in feature.qualifiers:
123
- if query.lower() in " ".join(self.flatten_list(feature.qualifiers.values())[:-1]) and feature.type != "source" and feature.type != "gene": # If search matches the feature's qualifiers somewhere, save it
124
- self.results_list.append((j+1,feature))
125
- else: # If search not in the feature's qualifiers, move to the next feature
126
- continue
127
- else:
128
- if query.lower() in " ".join(self.flatten_list(feature.qualifiers.values())) and feature.type != "source" and feature.type != "gene": # If search matches the feature's qualifiers somewhere, save it
129
- self.results_list.append((j+1,feature))
130
- else: # If search not in the feature's qualifiers, move to the next feature
131
- continue
132
- return self.results_list
133
-
134
- except Exception as e:
135
- logger.critical("Error in genbank_search() in annotation parser.")
136
- logger.critical(e)
137
- logger.critical(traceback.format_exc())
138
- msgBox = QtWidgets.QMessageBox()
139
- msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
140
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
141
- msgBox.setWindowTitle("Fatal Error")
142
- msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
143
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
144
- msgBox.exec()
145
-
146
- exit(-1)
147
-
148
-
149
-
150
-
151
- # This function parses gff files and stores them in a dictionary
152
- # It also creates a parallel dictionary to use in searching
153
- # Precondition: ONLY TO BE USED WITH GFF FILES
154
- def gff_parse(self):
155
- try:
156
- self.reg_dict.clear()
157
- self.para_dict.clear()
158
- prevFirstIndex = ""
159
- indexNumber = 1
160
- fileStream = open(self.annotationFileName)
161
- data_base_file_name = GlobalSettings.CSPR_DB + "/" + "gff_database.db"
162
-
163
- # temp list will be the following each time it is put into the dictionary:
164
- # [Sequence ID (genomic accession or scaffold), the index number itself, the feature type (cds, gene, mrna), the start(-1), end, and the strand]
165
- tempList = list()
166
- currentLocusTag = ""
167
- para_dict_key_string = ""
168
-
169
- # initialize the data base (this is what parses it for me)
170
- print("Intializing the data base")
171
- db = gffutils.create_db(self.annotationFileName, dbfn=data_base_file_name, force=True, keep_order=True,
172
- merge_strategy='merge', sort_attribute_values=True)
173
- print("Finished intializing")
174
-
175
- # call the feature version of that data base now
176
- db = gffutils.FeatureDB(data_base_file_name, keep_order=True)
177
-
178
- # now we go through that data base and get the data we want
179
- for feature in db.all_features(limit=None, strand=None, featuretype=None, order_by=None, reverse=False,
180
- completely_within=False):
181
- # if the genomic accession/scaffold/chromseome changes, update the indexNumber
182
- if prevFirstIndex != feature.seqid and prevFirstIndex != "":
183
- indexNumber += 1
184
- # if we find a new gene, update the locus_tag/name
185
- if feature.featuretype == "gene" or feature.featuretype == 'pseudogene':
186
-
187
- # check and see if locus tag is in the attributes, go on the Name if locus_tag is not in there
188
- if 'locus_tag' in feature.attributes:
189
- currentLocusTag = feature.attributes['locus_tag'][0]
190
- else:
191
- currentLocusTag = feature.attributes["Name"][0]
192
-
193
- # once the locus tag changes, append it to the para_dict
194
- if para_dict_key_string != "":
195
- if para_dict_key_string not in self.para_dict:
196
- self.para_dict[para_dict_key_string] = list()
197
- self.para_dict[para_dict_key_string].append(currentLocusTag)
198
- else:
199
- if currentLocusTag not in self.para_dict[para_dict_key_string]:
200
- self.para_dict[para_dict_key_string].append(currentLocusTag)
201
- para_dict_key_string = ""
202
-
203
- tempList = [currentLocusTag, indexNumber, feature.featuretype, feature.start - 1, feature.end,
204
- feature.strand]
205
-
206
- # insert that locus tag/name into the dictionary
207
- if currentLocusTag not in self.reg_dict:
208
- self.reg_dict[currentLocusTag] = []
209
- self.reg_dict[currentLocusTag].append(tempList)
210
- elif currentLocusTag in self.reg_dict:
211
- self.reg_dict[currentLocusTag].append(tempList)
212
-
213
- # go through each of this child's children
214
- for child in db.children(feature.id, level=None, featuretype=None, order_by=None, reverse=False,
215
- limit=None, completely_within=False):
216
- tempList = [currentLocusTag, indexNumber, child.featuretype, child.start - 1, child.end, child.strand]
217
-
218
- # only insert it if it hasn't been inserted before
219
- if tempList not in self.reg_dict[currentLocusTag]:
220
- self.reg_dict[currentLocusTag].append(tempList)
221
-
222
- # now go through the other ones which are not region
223
- elif feature.featuretype != "region" and feature.featuretype != "telomere" and feature.featuretype != "origin_of_replication":
224
- tempList = [currentLocusTag, indexNumber, feature.featuretype, feature.start - 1, feature.end,
225
- feature.strand]
226
-
227
- # only insert if it hasn't been inserted before
228
- if tempList not in self.reg_dict[currentLocusTag]:
229
- self.reg_dict[currentLocusTag].append(tempList)
230
-
231
- # now same as above, go through the children again
232
- for child in db.children(feature.id, level=None, featuretype=None, order_by=None, reverse=False,
233
- limit=None, completely_within=False):
234
- tempList = [currentLocusTag, indexNumber, child.featuretype, child.start - 1, child.end,
235
- child.strand]
236
-
237
- if tempList not in self.reg_dict[currentLocusTag]:
238
- self.reg_dict[currentLocusTag].append(tempList)
239
-
240
- # now we need to get the para_dict up and running
241
- # get the stuff out of the product part
242
- if 'product' in feature.attributes and feature.featuretype == "CDS":
243
- if para_dict_key_string == "":
244
- para_dict_key_string = feature.attributes['product'][0]
245
- else:
246
- para_dict_key_string = para_dict_key_string + ";" + feature.attributes['product'][0]
247
- # get the stuff out of the Note part
248
- if 'Note' in feature.attributes:
249
- if para_dict_key_string == "":
250
- para_dict_key_string = feature.attributes['Note'][0]
251
- else:
252
- para_dict_key_string = para_dict_key_string + ";" + feature.attributes['Note'][0]
253
-
254
- prevFirstIndex = feature.seqid
255
- self.max_chrom = indexNumber
256
- except Exception as e:
257
- logger.critical("Error in gff_parse() in annotation parser.")
258
- logger.critical(e)
259
- logger.critical(traceback.format_exc())
260
- msgBox = QtWidgets.QMessageBox()
261
- msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
262
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
263
- msgBox.setWindowTitle("Fatal Error")
264
- msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
265
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
266
- msgBox.exec()
267
-
268
- exit(-1)
269
-
270
- # This function parses txt files and stores them in a dictionary
271
- # It also creates a parallel dictionary to use in searching
272
- # Precondition: ONLY TO BE USED WITH TXT FILES
273
- def txt_parse(self):
274
- try:
275
- self.reg_dict.clear()
276
- prevGenAccession = ""
277
- indexNumber = 1
278
- fileStream = open(self.annotationFileName)
279
- buffer = ""
280
- currentLocusTag = ""
281
- para_dict_key_string = ""
282
-
283
- while(True): # this loop breaks out when buffer string is empty
284
- buffer = fileStream.readline()
285
-
286
- if(buffer.startswith("#")): #skip lines that start with #
287
- continue
288
- else:
289
- if(len(buffer) <= 2): # break out once we reach the end of the file
290
- break
291
-
292
- splitLine = buffer[:-1].split("\t")
293
-
294
- # increment indexNumber when genomic access changes
295
- if prevGenAccession != splitLine[6] and prevGenAccession != "":
296
- indexNumber += 1
297
-
298
- # if parsing on locus_tag, use the locus_tag as the key for the dict
299
- if self.txtLocusTag:
300
- currentLocusTag = splitLine[16]
301
- values = [currentLocusTag, indexNumber, splitLine[0], int(splitLine[7]) - 1, int(splitLine[8]), splitLine[9]]
302
-
303
- if currentLocusTag not in self.reg_dict:
304
- self.reg_dict[currentLocusTag] = [values]
305
- elif currentLocusTag in self.reg_dict:
306
- self.reg_dict[currentLocusTag].append(values)
307
-
308
- # if no locus_tag, parse on product_accession, use the product_accession as the key for the dict
309
- elif not self.txtLocusTag:
310
- currentLocusTag = splitLine[10]
311
- values = [currentLocusTag, indexNumber, splitLine[0], int(splitLine[7]) - 1, int(splitLine[8]), splitLine[9]]
312
-
313
- if currentLocusTag not in self.reg_dict:
314
- self.reg_dict[currentLocusTag] = [values]
315
- elif currentLocusTag in self.reg_dict:
316
- self.reg_dict[currentLocusTag].append(values)
317
-
318
- if splitLine[13] != '':
319
- if para_dict_key_string == '':
320
- para_dict_key_string = splitLine[13] + ';'
321
- else:
322
- para_dict_key_string = para_dict_key_string + splitLine[13] + ';'
323
-
324
- # leaving this in for now, it's related accession
325
- #if splitLine[12] != '':
326
- # if para_dict_key_string == '':
327
- # para_dict_key_string = splitLine[12] + ';'
328
- # else:
329
- # para_dict_key_string = para_dict_key_string + splitLine[12] + ';'
330
-
331
-
332
- if splitLine[14] != '':
333
- if para_dict_key_string == '':
334
- para_dict_key_string = splitLine[14] + ';'
335
- else:
336
- para_dict_key_string = para_dict_key_string + splitLine[14] + ';'
337
-
338
- para_dict_key_string = para_dict_key_string.replace(',', '')
339
- # set the parallel dictionary's key string
340
- #para_dict_key_string = splitLine[13] + ";" + splitLine[12] + ";" + splitLine[14]
341
-
342
- # if the current line we're on has the data we want for the parellel dictionary, store it
343
- if len(para_dict_key_string) > 3:
344
- if para_dict_key_string[len(para_dict_key_string) - 1] == ';':
345
- para_dict_key_string = para_dict_key_string[0:len(para_dict_key_string) - 1]
346
-
347
- if para_dict_key_string not in self.para_dict: # make a new input into the dict
348
- self.para_dict[para_dict_key_string] = [currentLocusTag]
349
- elif para_dict_key_string in self.para_dict:
350
- if currentLocusTag not in self.para_dict[para_dict_key_string]:
351
- # only append it to the dict's list if it isn't currently in there
352
- self.para_dict[para_dict_key_string].append(currentLocusTag)
353
-
354
- para_dict_key_string = ""
355
- prevGenAccession = splitLine[6]
356
- self.max_chrom = indexNumber
357
- except Exception as e:
358
- logger.critical("Error in txt_parse() in annotation parser.")
359
- logger.critical(e)
360
- logger.critical(traceback.format_exc())
361
- msgBox = QtWidgets.QMessageBox()
362
- msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
363
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
364
- msgBox.setWindowTitle("Fatal Error")
365
- msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
366
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
367
- msgBox.exec()
368
-
369
- exit(-1)
370
-
371
- # This function checks to see which file we are parsing
372
- # It also checks whether to parse based on locus_tag or product accession (txt files only)
373
- # Then it calls the respective parser functions used
374
- def find_which_file_version(self):
375
- try:
376
- if self.annotationFileName == "" or GlobalSettings.mainWindow.annotation_files.currentText() == "None":
377
- return -1
378
- if "gff" in self.annotationFileName:
379
- ### gff file support currently deprecated
380
- """
381
- self.isGff = True
382
- self.gff_parse()
383
- """
384
- print("Error: Wrong annotation file format")
385
- return -1
386
-
387
- elif "feature_table" in self.annotationFileName:
388
- ### feature table file support currently deprecated
389
- # now that we know it's a txt file and not a gff, check and see if we will be parsing by locus tag or
390
- # product accession
391
- """
392
- fileStream = open(self.annotationFileName)
393
-
394
- #skip all of the lines that start with #
395
- buf = fileStream.readline()
396
- while buf.startswith("#"):
397
- buf = fileStream.readline()
398
-
399
- # split it and see if the locus tag spot has data in it
400
- split = buf.split("\t")
401
- if split[16] != "": # if it does, we are parsing based on locus_tag
402
- self.txtLocusTag = True
403
- elif split[16] == "": # if not, we are parsing based on product accession
404
- self.txtLocusTag = False
405
- fileStream.close()
406
- self.isTxt = True
407
- self.txt_parse()
408
- """
409
- print("Error: Wrong annotation file format")
410
- return -1
411
- elif "gbff" or "gbk" in self.annotationFileName:
412
- return "gbff"
413
- # return -1 to throw the error window in main
414
- else:
415
- return -1
416
- except Exception as e:
417
- logger.critical("Error in find_which_file_version() in annotation parser.")
418
- logger.critical(e)
419
- logger.critical(traceback.format_exc())
420
- msgBox = QtWidgets.QMessageBox()
421
- msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
422
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
423
- msgBox.setWindowTitle("Fatal Error")
424
- msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
425
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
426
- msgBox.exec()
427
-
428
- exit(-1)
429
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/CMainWindow.py DELETED
@@ -1,987 +0,0 @@
1
- import platform
2
- # import controllers.ncbi as ncbi
3
- import os
4
- # from utils.Algorithms import get_table_headers
5
- # from models.CSPRparser import CSPRparser
6
- import glob
7
- import models.GlobalSettings as GlobalSettings
8
- from PyQt6 import QtWidgets, QtGui, QtCore, uic, QtGui
9
- from utils.ui import scale_ui, center_ui, show_message, show_error
10
- # from views.annotation_functions import *
11
- # from views.AnnotationParser import Annotation_Parser
12
- # from views.AnnotationWindow import AnnotationWindow
13
- # import views.genomeBrowser as genomeBrowser
14
- # from views.NewGenome import NewGenome
15
- # from views.NewEndonuclease import NewEndonuclease
16
- # from controllers.CoTargeting import CoTargeting
17
- # from views.generateLib import genLibrary
18
- # from controllers.Results import Results
19
- # from views.export_tool import export_tool
20
- # from views.closingWin import closingWindow
21
- # from utils.web import ncbi_page, repo_page, ncbi_blast_page
22
- # from controllers.populate_fna_files import PopulateFNAFiles
23
-
24
- # logger = GlobalSettings.logger
25
-
26
- fontSize = 12
27
-
28
- class CMainWindow(QtWidgets.QMainWindow):
29
- def __init__(self, settings):
30
- try:
31
- super(CMainWindow, self).__init__()
32
- # uic.loadUi(os.path.join(self.settings.get_ui_dir(), 'startupCASPER.ui'), self)
33
- # uic.loadUi(GlobalSettings.appdir + 'ui/CASPER_main.ui', self)
34
- # print("path: ", GlobalSettings.appdir + 'ui/CASPER_main_copy_2.ui')
35
- # uic.loadUi(GlobalSettings.appdir + 'ui/CASPER_main_copy_2.ui', self)
36
- self.settings = settings
37
- print("path: ", os.path.join(self.settings.get_ui_dir(), 'CASPER_main.ui'))
38
- uic.loadUi(os.path.join(self.settings.get_ui_dir(), 'CASPER_main.ui'), self)
39
- self.setWindowTitle("CASPER")
40
- self.setWindowIcon(QtGui.QIcon(os.path.join(self.settings.get_assets_dir(), "cas9image.ico")))
41
-
42
- # self.dbpath = ""
43
- # self.inputstring = "" # This is the search string
44
- # # self.info_path = settings.get_app_dir()
45
- # # info_path = settings.get_app_dir()
46
- # self.anno_name = ""
47
- # self.endo_name = ""
48
- # self.fontSize = 12
49
- # self.org = ""
50
- # self.TNumbers = {} # the T numbers from a kegg search
51
- # self.orgcodes = {} # Stores the Kegg organism code by the format {full name : organism code}
52
- # self.gene_list = {} # list of genes (no ides what they pertain to
53
- # self.searches = {}
54
- # self.checkBoxes = []
55
- # self.genlib_list = [] # This list stores selected SeqFeatures from annotation window
56
- # self.checked_info = {}
57
- # self.check_ntseq_info = {} # the ntsequences that go along with the checked_info
58
- # self.annotation_parser = Annotation_Parser()
59
- # self.link_list = list() # the list of the downloadable links from the NCBI search
60
- # self.organismDict = dict() # the dictionary for the links to download. Key is the description of the organism, value is the ID that can be found in link_list
61
- # self.results_list = list()
62
- # self.organismData = list()
63
- # self.ncbi = ncbi.NCBI_search_tool()
64
-
65
- # groupbox_style = """
66
- # QGroupBox:title{subcontrol-origin: margin;
67
- # left: 10px;
68
- # padding: 0 5px 0 5px;}
69
- # QGroupBox#Step1{border: 2px solid rgb(111,181,110);
70
- # border-radius: 9px;
71
- # margin-top: 10px;
72
- # font: bold 14pt 'Arial';}
73
- # """
74
-
75
- # self.Step1.setStyleSheet(groupbox_style)
76
- # self.Step2.setStyleSheet(groupbox_style.replace("Step1", "Step2"))
77
- # self.Step3.setStyleSheet(groupbox_style.replace("Step1", "Step3"))
78
- # self.CASPER_Navigation.setStyleSheet(groupbox_style.replace("Step1", "CASPER_Navigation").replace("solid","dashed").replace("rgb(111,181,110)","rgb(88,89,91)"))
79
-
80
- # self.setWindowIcon(QtGui.QIcon(GlobalSettings.appdir + "cas9image.ico"))
81
- # self.pushButton_FindTargets.clicked.connect(self.gather_settings)
82
- # self.pushButton_ViewTargets.clicked.connect(self.view_results)
83
- # self.pushButton_ViewTargets.setEnabled(False)
84
- # self.GenerateLibrary.setEnabled(False)
85
- # self.radioButton_Gene.clicked.connect(self.toggle_annotation)
86
- # self.radioButton_Position.clicked.connect(self.toggle_annotation)
87
-
88
- """ Connect functions to buttons """
89
- # self.newGenome_button.clicked.connect(self.launch_newGenome) # Connect launch function to New Genome
90
- # self.newEndo_button.clicked.connect(self.launch_newEndonuclease) # Connect launch function to New Endonuclease
91
- # self.multitargeting_button.clicked.connect(self.changeto_multitargeting) # Connect launch function to Multitargeting
92
- # self.populationAnalysis_button.clicked.connect(self.changeto_population_Analysis) # Connect launch function to PA
93
- # self.GenerateLibrary.clicked.connect(self.prep_genlib)
94
- # self.combineFiles_button.clicked.connect(self.launch_populate_fna_files)
95
-
96
- """ Connect functions to actions (menu bar) """
97
- # self.actionOpen_Genome_Browser.triggered.connect(self.launch_newGenomeBrowser)
98
- # self.actionExit.triggered.connect(self.close_app)
99
- # self.visit_repo.triggered.connect(repo_page)
100
- # self.actionChange_Directory.triggered.connect(self.change_directory)
101
- # self.actionNCBI.triggered.connect(ncbi_page)
102
- # self.actionCasper2.triggered.connect(self.open_casper2_web_page)
103
- # self.actionNCBI_BLAST.triggered.connect(ncbi_blast_page)
104
-
105
-
106
-
107
- # self.progressBar.setMinimum(0)
108
- # self.progressBar.setMaximum(100)
109
- # self.progressBar.reset()
110
- # self.Annotation_Window = AnnotationWindow(info_path)
111
- # self.geneEntryField.setPlaceholderText("Example Inputs: \n\n"
112
- # "Option 1: Feature (ID, Locus Tag, or Name)\n"
113
- # "Example: 854068/YOL086C/ADH1 for S. cerevisiae alcohol dehydrogenase 1\n\n"
114
- # "Option 2: Position (chromosome,start,stop)\n"
115
- # "Example: 1,1,1000 for targeting chromosome 1, base pairs 1 to 1000\n\n"
116
- # "Option 3: Sequence (must be within the selected organism)\n"
117
- # "Example: Any nucleotide sequence between 100 and 10,000 base pairs.\n\n"
118
- # "*Note: to multiplex, separate multiple queries by new lines*\n"
119
- # "Example:\n"
120
- # "1,1,1000\n"
121
- # "5,1,500\n"
122
- # "etc.")
123
-
124
- # show functionalities on window
125
- self.populate_fna_files = None
126
- self._new_genome = None
127
- # self.newEndonuclease = NewEndonuclease()
128
- # self.CoTargeting = CoTargeting(info_path)
129
- # self.Results = Results()
130
- # self.export_tool_window = export_tool()
131
- # self.genLib = genLibrary()
132
- # self.myClosingWindow = closingWindow()
133
- # self.genomebrowser = genomeBrowser.genomebrowser()
134
- # self.launch_ncbi_button.clicked.connect(self.launch_ncbi)
135
-
136
- # self.first_show = True
137
- scale_ui(self, custom_scale_width=1150, custom_scale_height=650)
138
- # self.show()
139
- # self.load_dropdown_data()
140
- print("MainWindow initialized")
141
- except Exception as e:
142
- show_error("Error in __init__() in main", e)
143
-
144
- # def get_populate_fna_files(self):
145
- # if self.populate_fna_files is None:
146
- # self.populate_fna_files = PopulateFNAFiles(GlobalSettings.GlobalSettings1(GlobalSettings.appdir))
147
- # return self.populate_fna_files
148
-
149
- # def launch_populate_fna_files(self):
150
- # self.get_populate_fna_files().show() # Ensure the window is shown
151
-
152
- # this function prepares everything for the generate library function
153
- # it is very similar to the gather settings, how ever it stores the data instead of calling the Annotation Window class
154
- # it moves the data onto the generateLib function, and then opens that window
155
- # def prep_genlib(self):
156
- # # make sure the user actually inputs something
157
- # try:
158
- # inputstring = str(self.geneEntryField.toPlainText())
159
- # if (inputstring.startswith("Example Inputs:") or inputstring == ""):
160
- # show_message(
161
- # fontSize=12,
162
- # icon=QtWidgets.QMessageBox.Icon.Critical,
163
- # title="Error",
164
- # message="No gene has been entered. Please enter a gene.",
165
- # button=QtWidgets.QMessageBox.StandardButton.Ok
166
- # )
167
- # return
168
- # else:
169
- # # standardize the input
170
- # inputstring = inputstring.lower()
171
- # found_matches_bool = True
172
- # # call the respective function
173
- # self.progressBar.setValue(10)
174
- # if self.radioButton_Gene.isChecked():
175
- # if len(self.genlib_list) > 0:
176
- # found_matches_bool = True
177
- # else:
178
- # found_matches_bool = False
179
- # elif self.radioButton_Position.isChecked() or self.radioButton_Sequence.isChecked():
180
- # show_message(
181
- # fontSize=12,
182
- # icon=QtWidgets.QMessageBox.Icon.Critical,
183
- # title="Error",
184
- # message="Generate Library can only work with feature searches.",
185
- # button=QtWidgets.QMessageBox.StandardButton.Ok
186
- # )
187
- # return
188
- # """
189
- # elif self.radioButton_Position.isChecked():
190
- # pinput = inputstring.split(';')
191
- # found_matches_bool = self.run_results("position", pinput,openAnnoWindow=False)
192
- # elif self.radioButton_Sequence.isChecked():
193
- # sinput = inputstring
194
- # found_matches_bool = self.run_results("sequence", sinput, openAnnoWindow=False)
195
- # """
196
- # # if matches are found
197
- # if found_matches_bool == True:
198
- # # get the cspr file name
199
- # cspr_file = self.organisms_to_files[self.orgChoice.currentText()][self.endoChoice.currentText()][0]
200
- # if platform.system() == 'Windows':
201
- # cspr_file = GlobalSettings.CSPR_DB + '\\' + cspr_file
202
- # else:
203
- # cspr_file = GlobalSettings.CSPR_DB + '/' + cspr_file
204
- # kegg_non = 'non_kegg'
205
-
206
- # # launch generateLib
207
- # self.progressBar.setValue(100)
208
-
209
- # # calculate the total number of matches found
210
- # tempSum = len(self.genlib_list)
211
-
212
- # # warn the user if the number is greater than 50
213
- # if tempSum > 50:
214
- # msgBox = QtWidgets.QMessageBox()
215
- # msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
216
- # msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
217
- # msgBox.setWindowTitle("Many Matches Found")
218
- # msgBox.setText("More than 50 matches have been found. Continuing could cause a slow down...\n\n Do you wish to continue?")
219
- # msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
220
- # msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
221
- # msgBox.exec()
222
-
223
- # if (msgBox.result() == QtWidgets.QMessageBox.No):
224
- # self.searches.clear()
225
- # self.progressBar.setValue(0)
226
- # return -2
227
-
228
- # self.genLib.launch(self.genlib_list,cspr_file, kegg_non)
229
- # else:
230
- # self.progressBar.setValue(0)
231
- # except Exception as e:
232
- # show_error("Error in prep_genlib() in main", e)
233
-
234
- # # Function for collecting the settings from the input field and transferring them to run_results
235
- # def gather_settings(self):
236
- # try:
237
- # ### If user searches multiple times for the same thing, this avoids re-searching the entire annotation file
238
- # check_org = self.orgChoice.currentText().lower()
239
- # check_endo = self.endoChoice.currentText().lower()
240
- # check_anno_name = self.annotation_files.currentText().lower()
241
- # check_input = str(self.geneEntryField.toPlainText()).lower()
242
- # if (check_input == self.inputstring and check_org == self.org and check_anno_name == self.anno_name and check_endo == self.endo_name):
243
- # same_search = True
244
- # else:
245
- # self.org = check_org
246
- # self.anno_name = check_anno_name
247
- # self.inputstring = check_input
248
- # self.endo_name = check_endo
249
- # same_search = False
250
-
251
- # # Error check: make sure the user actually inputs something
252
- # if (self.inputstring.startswith("Example Inputs:") or self.inputstring == ""):
253
- # show_message(
254
- # fontSize=12,
255
- # icon=QtWidgets.QMessageBox.Icon.Critical,
256
- # title="Error",
257
- # message="No feature has been searched for. Please enter a search.",
258
- # button=QtWidgets.QMessageBox.StandardButton.Ok
259
- # )
260
- # return
261
- # else:
262
-
263
- # ### Remove additional scoring columns if necessary
264
- # header = get_table_headers(self.Results.targetTable) # Returns headers of the target table in View Targets window
265
- # col_indices = [header.index(x) for x in GlobalSettings.algorithms if x in header] # Returns the index(es) of the alternative scoring column(s) in the target table of View Targets window
266
- # if len(col_indices) > 0: # If alternative scoring has been done
267
- # for i in col_indices:
268
- # self.Results.targetTable.removeColumn(i)
269
- # self.Results.targetTable.resizeColumnsToContents()
270
-
271
- # self.progressBar.setValue(10)
272
- # if self.radioButton_Gene.isChecked():
273
- # ginput = [x.strip() for x in self.inputstring.split('\n')] # Split search based on newline character and remove deadspace
274
- # self.run_results("feature", ginput, same_search)
275
- # elif self.radioButton_Position.isChecked():
276
- # pinput = [x.strip() for x in self.inputstring.split('\n')] # Split search based on newline character and remove deadspace
277
- # self.run_results("position", pinput, same_search)
278
- # elif self.radioButton_Sequence.isChecked():
279
- # sinput = self.inputstring
280
- # self.run_results("sequence", sinput, same_search)
281
- # except Exception as e:
282
- # show_error("Error in gather_settings() in main", e)
283
-
284
- # # ---- Following functions are for running the auxillary algorithms and windows ---- #
285
- # # this function is parses the annotation file given, and then goes through and goes onto results
286
- # # it will call other versions of collect_table_data and fill_table that work with these file types
287
- # # this function should work with the any type of annotation file, besides kegg.
288
- # # this assumes that the parsers all store the data the same way, which gff and feature table do
289
- # # please make sure the genbank parser stores the data in the same way
290
- # # so far the gff files seems to all be different. Need to think about how we want to parse it
291
- # def run_results_own_ncbi_file(self, inputstring, fileName, same_search, openAnnoWindow=True):
292
- # try:
293
- # self.set_progress(35)
294
- # self.results_list = self.annotation_parser.genbank_search(inputstring, same_search)
295
-
296
- # cspr_file = self.organisms_to_files[self.orgChoice.currentText()][self.endoChoice.currentText()][0]
297
- # cspr_file = os.path.join(GlobalSettings.CSPR_DB, cspr_file)
298
-
299
- # own_cspr_parser = CSPRparser(cspr_file)
300
- # own_cspr_parser.read_first_lines()
301
- # if len(own_cspr_parser.karystatsList) != self.annotation_parser.max_chrom:
302
- # show_message(
303
- # fontSize=12,
304
- # icon=QtWidgets.QMessageBox.Icon.Warning,
305
- # title="Warning:",
306
- # message="The number of chromosomes do not match. This could cause errors.",
307
- # button=QtWidgets.QMessageBox.StandardButton.Ok
308
- # )
309
- # self.set_progress(60)
310
-
311
- # self.searches.clear()
312
-
313
- # self.set_progress(75)
314
- # if not self.results_list:
315
- # show_message(
316
- # fontSize=12,
317
- # icon=QtWidgets.QMessageBox.Icon.Critical,
318
- # title="No Matches Found",
319
- # message="No matches found with that search, please try again.",
320
- # button=QtWidgets.QMessageBox.StandardButton.Ok
321
- # )
322
- # self.set_progress(0)
323
- # return False if not openAnnoWindow else None
324
-
325
- # self.set_progress(80)
326
-
327
- # return self.Annotation_Window.fill_table_nonKegg(self, self.results_list) if openAnnoWindow else True
328
- # except Exception as e:
329
- # show_error(f"Error in run_results_own_ncbi_file() in main.", e)
330
-
331
- # def set_progress(self, value):
332
- # self.progressBar.setValue(value)
333
-
334
- # def run_results(self, inputtype, inputstring, same_search, openAnnoWindow=True):
335
- # try:
336
- # file_name = self.annotation_files.currentText()
337
- # for file in glob.glob(GlobalSettings.CSPR_DB + "/**/*.gb*", recursive=True):
338
- # if file_name in file:
339
- # self.annotation_parser.annotationFileName = file
340
- # break
341
- # self.Results.annotation_path = self.annotation_parser.annotationFileName
342
-
343
- # progvalue = 15
344
- # self.searches = {}
345
- # self.gene_list = {}
346
- # self.progressBar.setValue(progvalue)
347
-
348
- # try:
349
- # self.Results.endonucleaseBox.currentIndexChanged.disconnect()
350
- # except Exception as e:
351
- # pass
352
- # # set Results endo combo box
353
- # self.Results.endonucleaseBox.clear()
354
-
355
- # # set the results window endoChoice box menu
356
- # # set the mainWindow's endoChoice first, and then loop through and set the rest of them
357
- # self.Results.endonucleaseBox.addItem(self.endoChoice.currentText())
358
- # for item in self.organisms_to_endos[str(self.orgChoice.currentText())]:
359
- # if item != self.Results.endonucleaseBox.currentText():
360
- # self.Results.endonucleaseBox.addItem(item)
361
-
362
- # self.Results.endonucleaseBox.currentIndexChanged.connect(self.Results.changeEndonuclease)
363
- # self.Results.get_endo_data()
364
-
365
- # # self.Results.change_start_end_button.setEnabled(False)
366
- # self.Results.displayGeneViewer.setChecked(0)
367
-
368
- # if inputtype == "feature":
369
- # fileType = self.annotation_parser.find_which_file_version()
370
-
371
- # # if the parser retuns the 'wrong file type' error
372
- # if fileType == -1:
373
- # show_message(
374
- # fontSize=12,
375
- # icon=QtWidgets.QMessageBox.Icon.Critical,
376
- # title="Error",
377
- # message="Feature search requires a GenBank formatted annotation file. Please select a file from the dropdown menu or search by position",
378
- # button=QtWidgets.QMessageBox.StandardButton.Ok
379
- # )
380
- # self.progressBar.setValue(0)
381
- # return
382
-
383
- # # make sure an annotation file has been selected
384
- # if self.annotation_files.currentText() == "None":
385
- # show_message(
386
- # fontSize=12,
387
- # icon=QtWidgets.QMessageBox.Icon.Critical,
388
- # title="Error",
389
- # message="Search by feature requires a GenBank annotation file. Please select one from the dropdown menu or search by position.",
390
- # button=QtWidgets.QMessageBox.StandardButton.Ok
391
- # )
392
- # self.progressBar.setValue(0)
393
- # return
394
-
395
- # # this now just goes onto the other version of run_results
396
- # myBool = self.run_results_own_ncbi_file(inputstring, self.annotation_files.currentText(), same_search, openAnnoWindow=openAnnoWindow)
397
- # if not openAnnoWindow:
398
- # return myBool
399
- # else:
400
- # self.progressBar.setValue(0)
401
- # return
402
-
403
- # if inputtype == "position":
404
- # full_org = str(self.orgChoice.currentText())
405
- # self.checked_info.clear()
406
- # self.check_ntseq_info.clear()
407
-
408
- # for item in inputstring: # Loop through each search
409
- # searchIndices = [x.strip() for x in item.split(',')] # Parse input query
410
-
411
- # if len(searchIndices) != 3:
412
- # show_message(
413
- # fontSize=12,
414
- # icon=QtWidgets.QMessageBox.Icon.Critical,
415
- # title="Error",
416
- # message="There are 3 arguments required for this function: chromosome, start position, and end position.",
417
- # button=QtWidgets.QMessageBox.StandardButton.Ok
418
- # )
419
- # self.progressBar.setValue(0)
420
- # return
421
-
422
- # if not searchIndices[0].isdigit() or not searchIndices[1].isdigit() or not searchIndices[2].isdigit():
423
- # show_message(
424
- # fontSize=12,
425
- # icon=QtWidgets.QMessageBox.Icon.Critical,
426
- # title="Error",
427
- # message="The positions given must be integers. Please try again.",
428
- # button=QtWidgets.QMessageBox.StandardButton.Ok
429
- # )
430
- # self.progressBar.setValue(0)
431
- # return
432
- # elif int(searchIndices[1]) >= int(searchIndices[2]):
433
- # show_message(
434
- # fontSize=12,
435
- # icon=QtWidgets.QMessageBox.Icon.Critical,
436
- # title="Error",
437
- # message="The start index must be less than the end index.",
438
- # button=QtWidgets.QMessageBox.StandardButton.Ok
439
- # )
440
- # self.progressBar.setValue(0)
441
- # return
442
- # elif abs(int(searchIndices[2])-int(searchIndices[1])) > 50000:
443
- # show_message(
444
- # fontSize=12,
445
- # icon=QtWidgets.QMessageBox.Icon.Critical,
446
- # title="Error",
447
- # message="The search range must be less than 50,000 nt.",
448
- # button=QtWidgets.QMessageBox.StandardButton.Ok
449
- # )
450
- # self.progressBar.setValue(0)
451
- # return
452
- # elif int(searchIndices[0]) > self.annotation_parser.get_max_chrom():
453
- # show_message(
454
- # fontSize=12,
455
- # icon=QtWidgets.QMessageBox.Icon.Critical,
456
- # title="Error",
457
- # message="Chromosome %s does not exist in the selected annotation file." % searchIndices[0],
458
- # button=QtWidgets.QMessageBox.StandardButton.Ok
459
- # )
460
- # self.progressBar.setValue(0)
461
- # return
462
- # # append the data into the checked_info
463
- # tempString = 'chrom: ' + str(searchIndices[0]) + ',start: ' + str(searchIndices[1]) + ',end: ' + str(searchIndices[2])
464
- # self.checked_info[tempString] = (int(searchIndices[0]), int(searchIndices[1])-1, int(searchIndices[2]))
465
-
466
- # self.progressBar.setValue(50)
467
- # self.Results.transfer_data(full_org, self.organisms_to_files[full_org], [str(self.endoChoice.currentText())], os.getcwd(), self.checked_info, self.check_ntseq_info,inputtype)
468
- # self.Results.load_gene_viewer()
469
- # self.progressBar.setValue(100)
470
- # self.pushButton_ViewTargets.setEnabled(True)
471
- # self.GenerateLibrary.setEnabled(True)
472
-
473
- # if inputtype == "sequence":
474
- # fileType = self.annotation_parser.find_which_file_version()
475
-
476
- # if fileType == -1:
477
- # show_message(
478
- # fontSize=12,
479
- # icon=QtWidgets.QMessageBox.Icon.Critical,
480
- # title="Error",
481
- # message="Search by sequence requires a GenBank annotation file. Please select one from the dropdown menu or search by position.",
482
- # button=QtWidgets.QMessageBox.StandardButton.Ok
483
- # )
484
- # self.progressBar.setValue(0)
485
- # return
486
- # if self.annotation_files.currentText() == "None":
487
- # show_message(
488
- # fontSize=12,
489
- # icon=QtWidgets.QMessageBox.Icon.Critical,
490
- # title="Error",
491
- # message="Search by sequence requires a GenBank annotation file. Please select one from the dropdown menu or search by position.",
492
- # button=QtWidgets.QMessageBox.StandardButton.Ok
493
- # )
494
- # self.progressBar.setValue(0)
495
- # return
496
-
497
- # checkString = 'AGTCN'
498
- # full_org = str(self.orgChoice.currentText())
499
- # self.checked_info.clear()
500
- # self.progressBar.setValue(10)
501
- # inputstring = inputstring.replace('\n','').upper().strip()
502
-
503
- # for letter in inputstring:
504
- # if letter not in checkString:
505
- # show_message(
506
- # fontSize=12,
507
- # icon=QtWidgets.QMessageBox.Icon.Critical,
508
- # title="Error",
509
- # message="The sequence must consist of A, G, T, C, or N. No other characters are allowed.",
510
- # button=QtWidgets.QMessageBox.StandardButton.Ok
511
- # )
512
- # self.progressBar.setValue(0)
513
- # return
514
-
515
- # if len(inputstring) < 100:
516
- # show_message(
517
- # fontSize=12,
518
- # icon=QtWidgets.QMessageBox.Icon.Critical,
519
- # title="Error",
520
- # message="The sequence given is too small. At least 100 characters are required.",
521
- # button=QtWidgets.QMessageBox.StandardButton.Ok
522
- # )
523
- # self.progressBar.setValue(0)
524
- # return
525
-
526
- # if len(inputstring) > 10000:
527
- # show_message(
528
- # fontSize=12,
529
- # icon=QtWidgets.QMessageBox.Icon.Question,
530
- # title="Large Sequence Detected",
531
- # message="The sequence given is too large one.\n\nPlease input a sequence less than 10kb in length.",
532
- # button=QtWidgets.QMessageBox.StandardButton.Yes
533
- # )
534
- # self.progressBar.setValue(0)
535
- # return
536
-
537
- # self.progressBar.setValue(30)
538
-
539
- # # Check the GBFF file for the sequence
540
- # my_check = self.annotation_parser.get_sequence_info(inputstring)
541
-
542
- # self.progressBar.setValue(55) # Update progress bar
543
-
544
- # if type(my_check) == bool:
545
- # show_message(
546
- # fontSize=12,
547
- # icon=QtWidgets.QMessageBox.Icon.Question,
548
- # title="Sequence Not Found",
549
- # message="The sequence entered was not found.\n\nPlease input a sequence that is in the selected organism.",
550
- # button=QtWidgets.QMessageBox.StandardButton.Yes
551
- # )
552
- # self.progressBar.setValue(0)
553
- # return
554
-
555
- # else:
556
- # tempString = 'chrom: ' + str(my_check[0]) + ',start: ' + str(my_check[1]) + ',end: ' + str(my_check[2])
557
- # self.checked_info[tempString] = (int(my_check[0]), int(my_check[1])-1, int(my_check[2]))
558
-
559
- # self.progressBar.setValue(75)
560
-
561
- # self.Results.transfer_data(full_org, self.organisms_to_files[full_org], [str(self.endoChoice.currentText())], os.getcwd(), self.checked_info, self.check_ntseq_info, inputtype)
562
- # self.Results.load_gene_viewer()
563
- # self.progressBar.setValue(100)
564
- # self.pushButton_ViewTargets.setEnabled(True)
565
- # self.GenerateLibrary.setEnabled(True)
566
- # except Exception as e:
567
- # show_error("Error in run_results() in main", e)
568
-
569
- # def handle_feature_search(self, input_string, open_anno_window):
570
- # file_type = self.annotation_parser.find_which_file_version()
571
- # if file_type == -1 or self.annotation_files.currentText() == "None":
572
- # self.show_error_message("Feature search requires a GenBank formatted annotation file.")
573
- # return False
574
-
575
- # return self.run_results_own_ncbi_file(input_string, self.annotation_files.currentText(), same_search, open_anno_window)
576
-
577
- # def launch_newGenome(self):
578
- # try:
579
- # # Update endo list
580
- # self.get_new_genome().fillEndo()
581
- # if self.get_new_genome().first_show:
582
- # center_ui(self.get_new_genome())
583
- # self.get_new_genome().first_show = False
584
- # self.hide()
585
- # self.get_new_genome().show()
586
- # except Exception as e:
587
- # show_error("Error in launch_newGenome() in main", e)
588
-
589
- # def launch_newEndonuclease(self):
590
- # try:
591
- # center_ui(self.newEndonuclease)
592
- # self.newEndonuclease.show()
593
- # self.newEndonuclease.activateWindow()
594
- # except Exception as e:
595
- # show_error("Error in launch_newEndonuclease() in main", e)
596
-
597
- # #launch genome browser tool
598
- # def launch_newGenomeBrowser(self):
599
- # try:
600
- # self.genomebrowser.createGraph(self)
601
- # except Exception as e:
602
- # show_error("Error in launch_newGenomeBrowser() in main", e)
603
-
604
- # def launch_ncbi(self):
605
- # try:
606
- # show_message(
607
- # fontSize=12,
608
- # icon=QtWidgets.QMessageBox.Icon.Information,
609
- # title="Note:",
610
- # message="NCBI Annotation Guidelines:\n\nDownload annotation files of the exact species and strain used in Analyze New Genome.\n\nMismatched annotation files will inhibit downstream analyses.",
611
- # button=QtWidgets.QMessageBox.StandardButton.Ok
612
- # )
613
- # if self.ncbi.first_show:
614
- # self.ncbi.first_show = False
615
- # center_ui(self.ncbi)
616
-
617
- # self.ncbi.show()
618
- # self.ncbi.activateWindow()
619
- # except Exception as e:
620
- # show_error("launch_ncbi() in main", e)
621
-
622
- # # this function does the same stuff that the other collect_table_data does, but works with the other types of files
623
- # def collect_table_data_nonkegg(self):
624
- # try:
625
- # # start out the same as the other collect_table_data
626
- # self.checked_info.clear()
627
- # self.genlib_list.clear()
628
- # self.check_ntseq_info.clear()
629
- # full_org = str(self.orgChoice.currentText())
630
- # holder = ()
631
- # selected_indices = []
632
- # selected_rows = self.Annotation_Window.tableWidget.selectionModel().selectedRows()
633
- # for ind in sorted(selected_rows):
634
- # selected_indices.append(ind.row())
635
-
636
- # for item in self.checkBoxes:
637
- # feature = item[1]
638
- # # If inidices of checkBoxes list and selected rows in table match...
639
- # if item[2] in selected_indices:
640
- # holder = (item[0],int(feature.location.start),int(feature.location.end)) # Tuple order: Feature chromosome/scaffold number, feature start, feature end
641
- # ### If locus tag available, combine with gene name to create dict key
642
- # if 'locus_tag' in feature.qualifiers:
643
- # tag = feature.qualifiers['locus_tag'][0]
644
- # key = tag + ": " + get_name(feature)
645
- # else:
646
- # key = get_name(feature)
647
- # self.checked_info[key] = holder
648
- # self.genlib_list.append((item[0],feature)) # Tuple order: Feature chromosome/scaffold number, SeqFeature object
649
- # else:
650
- # # If item was not selected in the table, go to the next item
651
- # continue
652
-
653
- # # now call transfer data
654
- # self.progressBar.setValue(95)
655
- # self.Results.transfer_data(full_org, self.organisms_to_files[full_org], [str(self.endoChoice.currentText())], os.getcwd(),
656
- # self.checked_info, self.check_ntseq_info,inputtype="feature")
657
- # self.Results.load_gene_viewer()
658
-
659
- # self.progressBar.setValue(100)
660
- # self.pushButton_ViewTargets.setEnabled(True)
661
- # self.GenerateLibrary.setEnabled(True)
662
- # except Exception as e:
663
- # show_error("Error in collect_table_data_nonkegg() in main", e)
664
-
665
- # def separate_line(self, input_string):
666
- # try:
667
- # export_array = []
668
- # while True:
669
- # index = input_string.find('\n')
670
- # if index == -1:
671
- # if len(input_string) == 0:
672
- # return export_array
673
- # else:
674
- # export_array.append(input_string)
675
- # return export_array
676
- # export_array.append(input_string[:index])
677
- # input_string = input_string[index + 1:]
678
- # except Exception as e:
679
- # show_error("Error in seperate_line() in main", e)
680
-
681
- # def removeWhiteSpace(self, strng):
682
- # try:
683
- # while True:
684
- # if len(strng) == 0 or (strng[0] != " " and strng[0] != "\n"):
685
- # break
686
- # strng = strng[1:]
687
- # while True:
688
- # if len(strng) == 0 or (strng[len(strng) - 1] != " " and strng[0] != "\n"):
689
- # return strng
690
- # strng = strng[:len(strng) - 1]
691
- # except Exception as e:
692
- # show_error("Error in removeWhiteSpace() in main", e)
693
-
694
- # # Function to enable and disable the Annotation function if searching by position or sequence
695
- # def toggle_annotation(self):
696
- # try:
697
- # if self.radioButton_Gene.isChecked():
698
- # self.Step2.setEnabled(True)
699
- # else:
700
- # self.Step2.setEnabled(True)
701
- # except Exception as e:
702
- # show_error("Error in toggle_annotation() in main", e)
703
-
704
- # def fill_annotation_dropdown(self):
705
- # try:
706
- # #recursive search for all GenBank files in casper db folder
707
- # self.annotation_files.clear()
708
- # annotation_files = glob.glob(GlobalSettings.CSPR_DB + "/**/*.gb*", recursive=True)
709
- # if platform.system() == "Windows":
710
- # for i in range(len(annotation_files)):
711
- # annotation_files[i] = annotation_files[i].replace("/","\\")
712
- # annotation_files[i] = annotation_files[i][annotation_files[i].rfind("\\") + 1:]
713
- # else:
714
- # for i in range(len(annotation_files)):
715
- # annotation_files[i] = annotation_files[i].replace("\\","/")
716
- # annotation_files[i] = annotation_files[i][annotation_files[i].rfind("/") + 1:]
717
-
718
- # annotation_files.sort(key=str.lower)
719
- # self.annotation_files.addItems(annotation_files)
720
- # self.annotation_files.addItems(["None"])
721
- # except Exception as e:
722
- # show_error("Error in fill_annotation_dropdown() in main", e)
723
-
724
- # def make_dictonary(self):
725
- # try:
726
- # url = "https://www.genome.jp/dbget-bin/get_linkdb?-t+genes+gn:" + self.TNumbers[
727
- # self.Annotations_Organism.currentText()]
728
- # source_code = requests.get(url, verify=False)
729
- # plain_text = source_code.text
730
- # buf = io.StringIO(plain_text)
731
-
732
- # while True:
733
- # line = buf.readline()
734
- # if line[0] == "-":
735
- # break
736
- # while True:
737
- # line = buf.readline()
738
- # if line[1] != "a":
739
- # return
740
- # line = line[line.find(">") + 1:]
741
- # seq = line[line.find(":") + 1:line.find("<")]
742
- # line = line[line.find(">") + 1:]
743
-
744
- # i = 0
745
- # while True:
746
- # if line[i] == " ":
747
- # i = i + 1
748
- # else:
749
- # break
750
- # key = line[i:line.find("\n") - 1]
751
- # if key in self.gene_list:
752
- # if seq not in self.gene_list[key]:
753
- # self.gene_list[key].append(seq)
754
- # else:
755
- # self.gene_list[key] = [seq]
756
- # z = 5
757
- # except Exception as e:
758
- # show_error("Error in make_dictionary() in main", e)
759
-
760
- # def organism_finder(self, long_str):
761
- # try:
762
- # semi = long_str.find(";")
763
- # index = 1
764
- # while True:
765
- # if long_str[semi - index] == " ":
766
- # break
767
- # index = index + 1
768
- # return long_str[:semi - index]
769
- # except Exception as e:
770
- # show_error("Error in organism_finder() in main", e)
771
-
772
- # # This method is for testing the execution of a button call to make sure the button is linked properly
773
- # def testexe(self):
774
- # try:
775
- # msgBox = QtWidgets.QMessageBox()
776
- # msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
777
- # msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
778
- # msgBox.setWindowTitle("Extract!")
779
- # msgBox.setText(
780
- # "Are you sure you want to quit?")
781
- # msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
782
- # msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
783
- # msgBox.exec()
784
-
785
- # if msgBox.result() == QtWidgets.QMessageBox.Yes:
786
- # # print(self.orgChoice.currentText())
787
- # sys.exit()
788
- # else:
789
- # pass
790
- # except Exception as e:
791
- # show_error("Error in testexe() in main", e)
792
-
793
- # def getData(self):
794
- # try:
795
- # try:
796
- # self.orgChoice.currentIndexChanged.disconnect()
797
- # except Exception as e:
798
- # pass
799
-
800
- # self.orgChoice.clear()
801
- # self.endoChoice.clear()
802
- # mypath = os.getcwd()
803
- # found = False
804
- # self.dbpath = mypath
805
- # onlyfiles = [str(f) for f in os.listdir(mypath) if os.path.isfile(os.path.join(mypath, f))]
806
- # onlyfiles.sort(key=str.lower)
807
- # self.organisms_to_files = {}
808
- # self.organisms_to_endos = {}
809
- # first = True
810
- # for file in onlyfiles:
811
- # if file.find('.cspr') != -1:
812
- # if first == True:
813
- # first = False
814
- # found = True
815
- # newname = file[0:-4]
816
- # endo = newname[newname.rfind("_")+1:-1]
817
- # hold = open(file, 'r')
818
- # buf = (hold.readline())
819
- # buf = str(buf)
820
- # buf = buf.strip()
821
- # species = buf.replace("GENOME: ",'')
822
-
823
- # if species in self.organisms_to_files:
824
- # self.organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
825
- # else:
826
- # self.organisms_to_files[species] = {}
827
- # self.organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
828
-
829
- # if species in self.organisms_to_endos:
830
- # self.organisms_to_endos[species].append(endo)
831
- # else:
832
- # self.organisms_to_endos[species] = [endo]
833
- # if self.orgChoice.findText(species) == -1:
834
- # self.orgChoice.addItem(species)
835
-
836
- # if found == False:
837
- # return False
838
-
839
- # self.endoChoice.clear()
840
- # self.endoChoice.addItems(self.organisms_to_endos[str(self.orgChoice.currentText())])
841
- # self.orgChoice.currentIndexChanged.connect(self.changeEndos)
842
- # except Exception as e:
843
- # show_error("Error in getData() in main.", e)
844
-
845
- # def changeEndos(self):
846
- # try:
847
- # if self.orgChoice.currentText() != "Custom Input Sequences":
848
- # self.Step2.setEnabled(True)
849
- # self.endoChoice.setEnabled(True)
850
- # self.radioButton_Gene.show()
851
- # self.radioButton_Position.show()
852
- # self.endoChoice.clear()
853
- # self.endoChoice.addItems(self.organisms_to_endos[str(self.orgChoice.currentText())])
854
- # else:
855
- # self.Step2.setEnabled(False)
856
- # self.endoChoice.clear()
857
- # self.endoChoice.setEnabled(False)
858
- # self.radioButton_Gene.hide()
859
- # self.radioButton_Position.hide()
860
- # except Exception as e:
861
- # show_error("Error in changeEndos() in main", e)
862
-
863
- # def change_directory(self):
864
- # try:
865
- # mydir = QtWidgets.QFileDialog.getExistingDirectory(
866
- # None, "Open a folder...", self.dbpath, QtWidgets.QFileDialog.Option.ShowDirsOnly)
867
-
868
- # if not os.path.isdir(mydir):
869
- # show_message(
870
- # fontSize=12,
871
- # icon=QtWidgets.QMessageBox.Icon.Critical,
872
- # title="Not a directory",
873
- # message="The directory you selected does not exist."
874
- # )
875
- # return
876
-
877
- # if not any(file.endswith(".cspr") for file in os.listdir(mydir)):
878
- # show_message(
879
- # fontSize=12,
880
- # icon=QtWidgets.QMessageBox.Icon.Critical,
881
- # title="Directory is invalid!",
882
- # message="You must select a directory with CSPR Files!"
883
- # )
884
- # return
885
-
886
- # os.chdir(mydir)
887
- # mydir = mydir.replace("/", "\\") if platform.system() == "Windows" else mydir
888
- # GlobalSettings.CSPR_DB = mydir
889
-
890
- # GlobalSettings.MTWin.directory = mydir
891
- # GlobalSettings.MTWin.get_data()
892
- # GlobalSettings.pop_Analysis.get_data()
893
- # self.getData()
894
- # self.fill_annotation_dropdown()
895
- # except Exception as e:
896
- # show_error("Error in change_directory() in main.", e)
897
-
898
- # def changeto_multitargeting(self):
899
- # try:
900
- # os.chdir(os.getcwd())
901
- # if GlobalSettings.MTWin.first_show == True:
902
- # GlobalSettings.MTWin.show()
903
- # GlobalSettings.MTWin.first_show = False
904
- # else:
905
- # GlobalSettings.MTWin.show()
906
- # GlobalSettings.mainWindow.hide()
907
-
908
- # except Exception as e:
909
- # show_error("Error in changeto_multitargeting() in main.", e)
910
-
911
- # #change to population analysis window
912
- # def changeto_population_Analysis(self):
913
- # try:
914
- # GlobalSettings.pop_Analysis.launch()
915
- # if GlobalSettings.pop_Analysis.first_show == True:
916
- # center_ui(GlobalSettings.pop_Analysis)
917
- # GlobalSettings.pop_Analysis.first_show = False
918
- # GlobalSettings.pop_Analysis.show()
919
- # GlobalSettings.mainWindow.hide()
920
- # except Exception as e:
921
- # show_error("Error in changeto_population_Analysis() in main.", e)
922
-
923
- # def annotation_information(self):
924
- # try:
925
- # show_message(
926
- # fontSize=12,
927
- # icon=QtWidgets.QMessageBox.Icon.Critical,
928
- # title="Annotation Information",
929
- # message="Annotation files are used for searching for spacers on a gene/locus basis and can be selected here using either " \
930
- # "NCBI databases or a local file."
931
- # )
932
- # except Exception as e:
933
- # show_error("Error in annotation_information() in main.", e)
934
-
935
- # @QtCore.pyqtSlot()
936
- # def view_results(self):
937
- # try:
938
- # #center results window on current screen
939
- # if self.Results.first_show == True:
940
- # self.Results.first_show = False
941
- # self.Results.centerUI()
942
-
943
- # self.Results.show()
944
- # self.hide()
945
- # except Exception as e:
946
- # show_error("Error in view_results() in main", e)
947
-
948
- # def closeFunction(self):
949
- # try:
950
- # # Attempt to close the NCBI window if it exists
951
- # try:
952
- # self.ncbi.close()
953
- # except AttributeError:
954
- # print("No NCBI window to close.")
955
-
956
- # self.myClosingWindow.get_files()
957
- # center_ui(self.myClosingWindow)
958
- # self.myClosingWindow.show()
959
- # except Exception as e:
960
- # show_error("Error in closeFunction() in main", e)
961
-
962
- # def close_app(self):
963
- # try:
964
- # # Attempt to close the NCBI window if it exists
965
- # try:
966
- # self.ncbi.close()
967
- # except Exception as e:
968
- # print("No NCBI window to close.")
969
-
970
- # self.closeFunction()
971
- # self.close()
972
- # except Exception as e:
973
- # show_error("Error in close_app() in main", e)
974
-
975
- # def load_dropdown_data(self):
976
- # """Fill in organism/endo/annotation dropdown information."""
977
- # try:
978
- # self.getData()
979
- # self.fill_annotation_dropdown()
980
- # # self.logger.debug("Successfully loaded organism/endo/annotation drop down information in Main.")
981
- # except Exception as e:
982
- # show_error("Error in load_dropdown_data() in Main", e)
983
-
984
- # # Call methods for other windows if needed
985
- # # self.load_mt_data()
986
- # # self.load_pop_analysis_data()
987
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/FindTargetsView.py CHANGED
@@ -1,7 +1,7 @@
1
  from PyQt6 import QtWidgets
2
  from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QHBoxLayout, QLabel
3
  from PyQt6 import uic
4
- from PyQt6.QtCore import Qt
5
 
6
  class FindTargetsView(QtWidgets.QMainWindow):
7
  def __init__(self, global_settings):
@@ -14,6 +14,11 @@ class FindTargetsView(QtWidgets.QMainWindow):
14
  self.results_table = self.findChild(QTableWidget, 'tblTargets')
15
  self.results_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
16
 
 
 
 
 
 
17
  # Set up the table columns
18
  self.results_table.setColumnCount(7)
19
  self.results_table.setHorizontalHeaderLabels([
@@ -23,50 +28,68 @@ class FindTargetsView(QtWidgets.QMainWindow):
23
 
24
  self.push_button_view_targets = self.findChild(QPushButton, 'pbtnViewTargets')
25
 
26
- # Add buttons
27
- # button_layout = QHBoxLayout()
28
- # self.export_button = QPushButton("Export")
29
- # self.view_targets_button = QPushButton("View Targets")
30
- # self.generate_library_button = QPushButton("Generate Library")
31
- # button_layout.addWidget(self.export_button)
32
- # button_layout.addWidget(self.view_targets_button)
33
- # button_layout.addWidget(self.generate_library_button)
34
-
35
- # Add result count label
36
- # self.result_count_label = QLabel("Results: 0")
37
-
38
- # main_layout = QVBoxLayout(self)
39
- # main_layout.addWidget(self.result_count_label)
40
- # main_layout.addWidget(self.results_table)
41
- # main_layout.addLayout(button_layout)
42
 
43
  def display_results(self, results):
 
 
 
 
 
44
  self.results_table.setRowCount(len(results))
45
 
 
46
  for row, result in enumerate(results):
47
- self.results_table.setItem(row, 0, QTableWidgetItem(result['feature_type']))
48
- self.results_table.setItem(row, 1, QTableWidgetItem(str(result['chromosome'])))
49
- self.results_table.setItem(row, 2, QTableWidgetItem(result['feature_id']))
50
- self.results_table.setItem(row, 3, QTableWidgetItem(result['feature_name']))
51
- self.results_table.setItem(row, 4, QTableWidgetItem(result['feature_description']))
52
- self.results_table.setItem(row, 5, QTableWidgetItem(result['location']))
53
- self.results_table.setItem(row, 6, QTableWidgetItem(result['strand']))
 
 
 
54
 
 
 
55
  self.results_table.resizeColumnsToContents()
56
- # self.result_count_label.setText(f"Results: {len(results)}")
 
57
 
58
  def get_selected_targets(self):
59
  selected_rows = set(index.row() for index in self.results_table.selectedIndexes())
60
  selected_targets = []
 
 
 
 
 
 
 
 
 
 
 
 
61
  for row in selected_rows:
62
  target = {
63
- 'feature_type': self.results_table.item(row, 0).text(),
64
- 'chromosome': self.results_table.item(row, 1).text(),
65
- 'feature_id': self.results_table.item(row, 2).text(),
66
- 'feature_name': self.results_table.item(row, 3).text(),
67
- 'feature_description': self.results_table.item(row, 4).text(),
68
- 'location': self.results_table.item(row, 5).text(),
69
- 'strand': self.results_table.item(row, 6).text()
70
  }
71
  selected_targets.append(target)
72
  return selected_targets
 
1
  from PyQt6 import QtWidgets
2
  from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QHBoxLayout, QLabel
3
  from PyQt6 import uic
4
+ from PyQt6.QtCore import Qt, QTimer
5
 
6
  class FindTargetsView(QtWidgets.QMainWindow):
7
  def __init__(self, global_settings):
 
14
  self.results_table = self.findChild(QTableWidget, 'tblTargets')
15
  self.results_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
16
 
17
+ # Optimize table performance
18
+ self.results_table.setUpdatesEnabled(False) # Disable updates during setup
19
+ self.results_table.setSortingEnabled(False) # Disable sorting during setup
20
+ self.results_table.horizontalHeader().setStretchLastSection(True)
21
+
22
  # Set up the table columns
23
  self.results_table.setColumnCount(7)
24
  self.results_table.setHorizontalHeaderLabels([
 
28
 
29
  self.push_button_view_targets = self.findChild(QPushButton, 'pbtnViewTargets')
30
 
31
+ # Pre-allocate items for better performance
32
+ self._cached_items = {}
33
+
34
+ def _get_table_item(self, text):
35
+ """Cache and reuse QTableWidgetItems for better performance"""
36
+ if text not in self._cached_items:
37
+ item = QTableWidgetItem(str(text))
38
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable) # Make item read-only
39
+ self._cached_items[text] = item
40
+ return self._cached_items[text].clone()
 
 
 
 
 
 
41
 
42
  def display_results(self, results):
43
+ # Disable updates for bulk operations
44
+ self.results_table.setUpdatesEnabled(False)
45
+ self.results_table.setSortingEnabled(False)
46
+
47
+ # Set row count once
48
  self.results_table.setRowCount(len(results))
49
 
50
+ # Batch insert items
51
  for row, result in enumerate(results):
52
+ self.results_table.setItem(row, 0, self._get_table_item(result['feature_type']))
53
+ self.results_table.setItem(row, 1, self._get_table_item(str(result['chromosome'])))
54
+ self.results_table.setItem(row, 2, self._get_table_item(result['feature_id']))
55
+ self.results_table.setItem(row, 3, self._get_table_item(result['feature_name']))
56
+ self.results_table.setItem(row, 4, self._get_table_item(result['feature_description']))
57
+ self.results_table.setItem(row, 5, self._get_table_item(result['location']))
58
+ self.results_table.setItem(row, 6, self._get_table_item(result['strand']))
59
+
60
+ # Re-enable updates and adjust columns
61
+ QTimer.singleShot(0, self._finish_table_update)
62
 
63
+ def _finish_table_update(self):
64
+ """Complete table update in the next event loop iteration"""
65
  self.results_table.resizeColumnsToContents()
66
+ self.results_table.setUpdatesEnabled(True)
67
+ self.results_table.setSortingEnabled(True)
68
 
69
  def get_selected_targets(self):
70
  selected_rows = set(index.row() for index in self.results_table.selectedIndexes())
71
  selected_targets = []
72
+
73
+ # Get column indices once
74
+ columns = {
75
+ 'feature_type': 0,
76
+ 'chromosome': 1,
77
+ 'feature_id': 2,
78
+ 'feature_name': 3,
79
+ 'feature_description': 4,
80
+ 'location': 5,
81
+ 'strand': 6
82
+ }
83
+
84
  for row in selected_rows:
85
  target = {
86
+ 'feature_type': self.results_table.item(row, columns['feature_type']).text(),
87
+ 'chromosome': self.results_table.item(row, columns['chromosome']).text(),
88
+ 'feature_id': self.results_table.item(row, columns['feature_id']).text(),
89
+ 'feature_name': self.results_table.item(row, columns['feature_name']).text(),
90
+ 'feature_description': self.results_table.item(row, columns['feature_description']).text(),
91
+ 'location': self.results_table.item(row, columns['location']).text(),
92
+ 'strand': self.results_table.item(row, columns['strand']).text()
93
  }
94
  selected_targets.append(target)
95
  return selected_targets
src/views/HomeWindowView.py CHANGED
@@ -124,3 +124,6 @@ class HomeWindowView(QWidget):
124
  return "sequence"
125
  else:
126
  return "feature" # Default to feature if somehow none are selected
 
 
 
 
124
  return "sequence"
125
  else:
126
  return "feature" # Default to feature if somehow none are selected
127
+
128
+ def get_annotation_file(self) -> str:
129
+ return self.combo_box_local_annotation_files.currentText()
src/views/MainWindowView.py CHANGED
@@ -13,53 +13,149 @@ from functools import partial
13
  import qdarktheme
14
 
15
  class CloseableTabWidget(QTabWidget):
16
- # Define a new signal that emits the closed widget
17
  tab_closed = pyqtSignal(QWidget)
18
 
19
  def __init__(self, parent=None):
20
  super().__init__(parent)
21
  self.setTabsClosable(False)
22
  self.tabCloseRequested.connect(self.closeTab)
 
 
23
 
24
  def closeTab(self, index):
25
- if self.count() > 1 and index != 0:
26
- widget = self.widget(index)
27
- self.removeTab(index)
28
- widget.deleteLater()
29
- # Emit the signal with the closed widget
30
- self.tab_closed.emit(widget)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  def addTab(self, widget, label):
33
- index = super().addTab(widget, label)
34
- print(f"Adding tab: {label}, Index: {index}")
35
- if index != 0:
36
- # Create a close button
37
- close_button = QToolButton(self.tabBar())
38
- close_icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_TitleBarCloseButton)
39
- close_button.setIcon(close_icon)
40
- close_button.setIconSize(QSize(16, 16))
41
- close_button.setAutoRaise(True)
42
-
43
- # Apply updated stylesheet with adjusted negative margin and border-radius
44
- close_button.setStyleSheet("""
45
- QToolButton {
46
- border: none;
47
- padding: 0px;
48
- }
49
- QToolButton:hover {
50
- background: #c42b1c;
51
  }
52
- """)
53
- close_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
54
- close_button.setFixedSize(18, 18) # Fixed size larger than icon to allow padding
55
-
56
- # Connect the close button's clicked signal using partial to pass the index
57
- close_button.clicked.connect(partial(self.closeTab, index))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- # Add the close button to the tab
60
- self.tabBar().setTabButton(index, QTabBar.ButtonPosition.RightSide, close_button)
61
-
62
- return index
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
 
65
  class MainWindowView(QMainWindow):
@@ -71,13 +167,38 @@ class MainWindowView(QMainWindow):
71
  self.oldPos = None
72
 
73
  def _init_ui(self) -> None:
 
 
 
74
  try:
75
- self._load_ui_file
 
 
 
 
 
76
  self._load_ui_file()
77
  self._init_window_properties()
78
  self._init_ui_elements()
 
79
  self._scale_ui()
80
- self.logger.debug(f"After _scale_ui call in _init_ui. Window size: {self.size()}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  except Exception as e:
82
  self._handle_init_error(e)
83
 
@@ -89,11 +210,16 @@ class MainWindowView(QMainWindow):
89
  """
90
  Creates a frameless, translucent window without a toolbar.
91
  """
 
92
  self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
93
  self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
 
 
94
  toolbars = self.findChildren(QtWidgets.QToolBar)
95
  for toolbar in toolbars:
96
  toolbar.hide()
 
 
97
 
98
  def _init_ui_elements(self) -> None:
99
  # Initialize menu bar and custom title bar
@@ -260,20 +386,40 @@ class MainWindowView(QMainWindow):
260
  # self.tab_widget.setCurrentIndex(tab_index)
261
 
262
 
263
- def _scale_ui(self) -> None:
264
- initial_size = self.size()
265
- self.logger.debug(f"Initial window size before scale_ui: {initial_size}")
266
- scale_ui(self, custom_scale_width=1000, custom_scale_height=350)
267
- final_size = self.size()
268
- self.logger.debug(f"Final window size after scale_ui: {final_size}")
269
-
270
- if initial_size == final_size:
271
- self.logger.warning("Window size did not change after scale_ui call")
272
- else:
273
- self.logger.info(f"Window size changed from {initial_size} to {final_size}")
274
-
275
- self.resize(575, 400)
276
- self.logger.debug(f"Forced resize to 1000x350. New size: {self.size()}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
  def _handle_init_error(self, e: Exception) -> None:
279
  error_msg = f"Error initializing MainWindowView: {str(e)}"
@@ -395,3 +541,14 @@ class MainWindowView(QMainWindow):
395
  background: {theme['tab_hover_bg_color']};
396
  }}
397
  """)
 
 
 
 
 
 
 
 
 
 
 
 
13
  import qdarktheme
14
 
15
  class CloseableTabWidget(QTabWidget):
 
16
  tab_closed = pyqtSignal(QWidget)
17
 
18
  def __init__(self, parent=None):
19
  super().__init__(parent)
20
  self.setTabsClosable(False)
21
  self.tabCloseRequested.connect(self.closeTab)
22
+ self._tabs = {} # Dictionary to keep track of tab widgets
23
+ self.tabBar().tabMoved.connect(self._handle_tab_moved)
24
 
25
  def closeTab(self, index):
26
+ try:
27
+ if self.count() > 1 and index != 0:
28
+ widget = self.widget(index)
29
+ if widget:
30
+ # Get tab text before removal
31
+ tab_text = self.tabText(index)
32
+
33
+ # Clean up the controller if it exists
34
+ controller = getattr(widget, 'controller', None)
35
+ if controller and hasattr(controller, 'model') and hasattr(controller.model, 'cleanup'):
36
+ controller.model.cleanup()
37
+
38
+ # Remove from tracking dictionary
39
+ if tab_text in self._tabs:
40
+ del self._tabs[tab_text]
41
+
42
+ # Remove the tab
43
+ self.removeTab(index)
44
+
45
+ # Emit signal before deletion
46
+ self.tab_closed.emit(widget)
47
+
48
+ # Schedule widget for deletion
49
+ widget.deleteLater()
50
+
51
+ # Update all remaining tabs
52
+ self._update_all_tabs()
53
+ except Exception as e:
54
+ print(f"Error closing tab: {e}")
55
 
56
  def addTab(self, widget, label):
57
+ try:
58
+ if widget and label:
59
+ # Store widget reference with unique identifier
60
+ tab_id = f"{label}_{id(widget)}"
61
+ self._tabs[tab_id] = {
62
+ 'widget': widget,
63
+ 'label': label,
64
+ 'close_button': None
 
 
 
 
 
 
 
 
 
 
65
  }
66
+
67
+ # Add the tab
68
+ index = super().addTab(widget, label)
69
+
70
+ if index != 0:
71
+ # Create and setup close button
72
+ close_button = self._create_close_button(index, label)
73
+ self._tabs[tab_id]['close_button'] = close_button
74
+ self.tabBar().setTabButton(index, QTabBar.ButtonPosition.RightSide, close_button)
75
+
76
+ return index
77
+ except Exception as e:
78
+ print(f"Error adding tab: {e}")
79
+ return -1
80
+
81
+ def _create_close_button(self, index, label):
82
+ """Create a new close button for a tab"""
83
+ close_button = QToolButton(self.tabBar())
84
+ close_button.setObjectName(f"close_button_{label}")
85
+ close_icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_TitleBarCloseButton)
86
+ close_button.setIcon(close_icon)
87
+ close_button.setIconSize(QSize(16, 16))
88
+ close_button.setAutoRaise(True)
89
+ close_button.setStyleSheet("""
90
+ QToolButton {
91
+ border: none;
92
+ padding: 0px;
93
+ }
94
+ QToolButton:hover {
95
+ background: #c42b1c;
96
+ }
97
+ """)
98
+ close_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
99
+ close_button.setFixedSize(18, 18)
100
+ close_button.clicked.connect(lambda checked, idx=index: self.safely_close_tab(idx))
101
+ return close_button
102
 
103
+ def safely_close_tab(self, index):
104
+ """Safely handle tab closure with error checking"""
105
+ try:
106
+ if 0 <= index < self.count():
107
+ current_widget = self.widget(index)
108
+ if current_widget and index != 0:
109
+ self.closeTab(index)
110
+ except Exception as e:
111
+ print(f"Error in safely_close_tab: {e}")
112
+
113
+ def _handle_tab_moved(self, from_index: int, to_index: int):
114
+ """Handle tab movement and update close buttons"""
115
+ try:
116
+ self._update_all_tabs()
117
+ except Exception as e:
118
+ print(f"Error handling tab movement: {e}")
119
+
120
+ def _update_all_tabs(self):
121
+ """Update all tabs and their close buttons"""
122
+ try:
123
+ for i in range(1, self.count()): # Skip index 0 (home tab)
124
+ widget = self.widget(i)
125
+ if widget:
126
+ label = self.tabText(i)
127
+ tab_id = f"{label}_{id(widget)}"
128
+
129
+ # Create new close button if needed
130
+ if tab_id not in self._tabs or not self._tabs[tab_id].get('close_button'):
131
+ close_button = self._create_close_button(i, label)
132
+ self._tabs[tab_id] = {
133
+ 'widget': widget,
134
+ 'label': label,
135
+ 'close_button': close_button
136
+ }
137
+ self.tabBar().setTabButton(i, QTabBar.ButtonPosition.RightSide, close_button)
138
+ else:
139
+ # Update existing close button's click connection
140
+ close_button = self._tabs[tab_id]['close_button']
141
+ close_button.clicked.disconnect()
142
+ close_button.clicked.connect(lambda checked, idx=i: self.safely_close_tab(idx))
143
+ except Exception as e:
144
+ print(f"Error updating tabs: {e}")
145
+
146
+ def moveTab(self, from_index, to_index):
147
+ """Override moveTab to safely handle tab movement"""
148
+ try:
149
+ if (0 <= from_index < self.count() and
150
+ 0 <= to_index < self.count() and
151
+ from_index != 0 and
152
+ to_index != 0):
153
+
154
+ super().moveTab(from_index, to_index)
155
+ self._update_all_tabs()
156
+
157
+ except Exception as e:
158
+ print(f"Error moving tab: {e}")
159
 
160
 
161
  class MainWindowView(QMainWindow):
 
167
  self.oldPos = None
168
 
169
  def _init_ui(self) -> None:
170
+ # Hide the window and disable updates during initialization
171
+ self.hide()
172
+ self.setUpdatesEnabled(False)
173
  try:
174
+ # Calculate center position first
175
+ screen = QtGui.QGuiApplication.primaryScreen()
176
+ screen_geometry = screen.geometry()
177
+ centerPoint = screen_geometry.center()
178
+
179
+ # Load and initialize UI
180
  self._load_ui_file()
181
  self._init_window_properties()
182
  self._init_ui_elements()
183
+ self.apply_theme()
184
  self._scale_ui()
185
+
186
+ # Get final size
187
+ final_size = self.size()
188
+
189
+ # Calculate position only once
190
+ x = centerPoint.x() - (final_size.width() // 2)
191
+ y = centerPoint.y() - (final_size.height() // 2)
192
+
193
+ # Set position and size in a single operation
194
+ self.setGeometry(x, y, final_size.width(), final_size.height())
195
+
196
+ # Re-enable updates and show window
197
+ self.setUpdatesEnabled(True)
198
+ self.show()
199
+ self.repaint() # Force immediate repaint
200
+
201
+ self.logger.debug(f"Window initialized at position ({x}, {y}) with size {final_size}")
202
  except Exception as e:
203
  self._handle_init_error(e)
204
 
 
210
  """
211
  Creates a frameless, translucent window without a toolbar.
212
  """
213
+ # Set window flags before other properties
214
  self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
215
  self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
216
+ self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
217
+ # Hide toolbars
218
  toolbars = self.findChildren(QtWidgets.QToolBar)
219
  for toolbar in toolbars:
220
  toolbar.hide()
221
+ # Ensure window starts hidden
222
+ self.setVisible(False)
223
 
224
  def _init_ui_elements(self) -> None:
225
  # Initialize menu bar and custom title bar
 
386
  # self.tab_widget.setCurrentIndex(tab_index)
387
 
388
 
389
+ def _scale_ui(self):
390
+ """Modified scale_ui to only handle sizing, not positioning"""
391
+ try:
392
+ screen = QtGui.QGuiApplication.primaryScreen()
393
+ screen_geometry = screen.geometry()
394
+ width = screen_geometry.width()
395
+ height = screen_geometry.height()
396
+
397
+ # Font scaling
398
+ self.centralWidget().setStyleSheet(f"font: 12pt 'Arial';")
399
+
400
+ if hasattr(self, 'title'):
401
+ scaled_title_font_size = int(30 * (width / 1920))
402
+ self.title.setStyleSheet(f"font: bold {scaled_title_font_size}pt 'Arial';")
403
+
404
+ # Calculate size only
405
+ scaledWidth = int((width * 575) / 1920)
406
+ scaledHeight = int((height * 400) / 1080)
407
+
408
+ # Ensure minimum size
409
+ self.adjustSize()
410
+ currentWidth = self.size().width()
411
+ currentHeight = self.size().height()
412
+
413
+ if scaledHeight < currentHeight:
414
+ scaledHeight = currentHeight
415
+ if scaledWidth < currentWidth:
416
+ scaledWidth = currentWidth
417
+
418
+ # Only resize, don't reposition
419
+ self.resize(scaledWidth, scaledHeight)
420
+
421
+ except Exception as e:
422
+ self.logger.error(f"Error in _scale_ui: {str(e)}")
423
 
424
  def _handle_init_error(self, e: Exception) -> None:
425
  error_msg = f"Error initializing MainWindowView: {str(e)}"
 
541
  background: {theme['tab_hover_bg_color']};
542
  }}
543
  """)
544
+
545
+ def show_window(self) -> None:
546
+ """Shows the window without repositioning"""
547
+ self.show()
548
+ self.repaint()
549
+
550
+
551
+
552
+
553
+
554
+
src/views/MultitargetingWindowView.py CHANGED
@@ -1,108 +1,370 @@
1
- from PyQt6 import QtWidgets, QtGui, QtCore
2
- from PyQt6.QtCore import Qt
 
3
  from PyQt6.QtGui import QIcon
4
- from PyQt6 import uic
5
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
6
  from matplotlib.figure import Figure
7
- import models.GlobalSettings as GlobalSettings
8
  from matplotlib.ticker import MaxNLocator
9
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
10
- from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
11
- from utils.ui import show_message, show_error, scale_ui, center_ui
12
- from PyQt6.QtWidgets import QWidget, QComboBox, QVBoxLayout, QPushButton
13
- import os
14
-
15
- class MultitargetingWindowView(QWidget):
16
- def __init__(self, settings):
17
  super().__init__()
18
- self.settings = settings
19
- self._init_ui()
 
 
20
 
21
- def _init_ui(self):
22
  try:
23
- self._load_ui_file()
24
- # self._init_ui_elements()
25
- # self._scale_ui()
26
  except Exception as e:
27
- self._handle_init_error(e)
28
 
29
- def _handle_init_error(self, e: Exception):
30
- error_msg = f"Error initializing MultitargetingWindowView: {str(e)}"
31
- self.settings.logger.error(error_msg, exc_info=True)
32
- show_error(self.settings, "Initialization Error", error_msg)
33
- raise
34
 
35
- def _load_ui_file(self):
36
- ui_file = os.path.join(self.settings.get_ui_dir(), "multitargeting_window.ui")
37
- uic.loadUi(ui_file, self)
 
 
 
 
38
 
39
- def scaleUI(self):
40
- # Implement scaling logic if needed
41
- pass
 
 
 
42
 
43
- def centerUI(self):
44
- # Implement centering logic if needed
45
- pass
46
 
47
- def update_table(self, data):
48
- # Implement table update logic
49
- pass
 
 
 
 
50
 
51
- def update_global_stats(self, model):
52
- # Implement global stats update logic
53
- pass
 
 
 
 
 
 
 
54
 
55
- def update_global_line_graph(self, data):
56
- # Implement line graph update logic
57
- pass
58
 
59
- def update_global_bar_graph(self, data):
60
- # Implement bar graph update logic
61
- pass
 
 
 
 
 
62
 
63
- # Add other methods as needed
 
64
 
65
- class MplCanvas(FigureCanvasQTAgg):
66
- def __init__(self, parent=None, width=5, height=4, dpi=100):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  try:
68
- fig = Figure(dpi=dpi, tight_layout=True)
69
- self.axes = fig.add_subplot(111)
70
- self.axes.clear()
71
- super(MplCanvas, self).__init__(fig)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  except Exception as e:
73
- show_error(self.settings, "Error initializing MplCanvas class in MultitargetingWindowView.", e)
74
 
75
- class LoadingWindow(QtWidgets.QMainWindow):
76
- def __init__(self):
77
  try:
78
- super(LoadingWindow, self).__init__()
79
- uic.loadUi(GlobalSettings.appdir + "ui/loading_data_form.ui", self)
80
- self.setWindowIcon(QIcon(GlobalSettings.appdir + "cas9image.ico"))
81
- self.loading_bar.setValue(0)
82
- self.setWindowTitle("Loading Data")
83
- scale_ui(self, custom_scale_width=450, custom_scale_height=125)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  except Exception as e:
85
- show_error(self.settings, "Error initializing LoadingWindow class in MultitargetingWindowView.", e)
86
 
87
- class MultitargetingStatisticsView(QtWidgets.QMainWindow):
88
- def __init__(self, parent=None):
89
  try:
90
- super(MultitargetingStatisticsView, self).__init__(parent)
91
- uic.loadUi(GlobalSettings.appdir + 'ui/multitargeting_stats.ui', self)
92
- self.setWindowIcon(QIcon(GlobalSettings.appdir + "cas9image.ico"))
93
- self.setWindowTitle("Statistics")
94
- scale_ui(self, custom_scale_width=275, custom_scale_height=185)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  except Exception as e:
96
- show_error(self.settings, "Error initializing MultitargetingStatisticsView class in MultitargetingWindowView.", e)
97
 
98
- class SqlQuerySettingsView(QtWidgets.QMainWindow):
99
- def __init__(self):
100
  try:
101
- super(SqlQuerySettingsView, self).__init__()
102
- uic.loadUi(GlobalSettings.appdir + "ui/multitargeting_sql_settings.ui", self)
103
- self.setWindowTitle("SQL Settings")
104
- self.setWindowIcon(QIcon(GlobalSettings.appdir + "cas9image.ico"))
105
- self.row_count.setValidator(QtGui.QIntValidator())
106
- scale_ui(self, custom_scale_width=375, custom_scale_height=140)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  except Exception as e:
108
- show_error(self.settings, "Error initializing SqlQuerySettingsView class in MultitargetingWindowView.", e)
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from PyQt6 import QtWidgets, uic, QtGui
3
+ from PyQt6.QtWidgets import QTableWidgetItem, QAbstractItemView
4
  from PyQt6.QtGui import QIcon
5
+ from PyQt6.QtCore import Qt
6
+ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT
7
  from matplotlib.figure import Figure
 
8
  from matplotlib.ticker import MaxNLocator
9
+ from utils.ui import show_error, scale_ui
10
+
11
+ class MultitargetingWindowView(QtWidgets.QMainWindow):
12
+ def __init__(self, global_settings):
 
 
 
 
13
  super().__init__()
14
+ self.settings = global_settings
15
+ self.logger = self.settings.get_logger()
16
+
17
+ self.init_ui()
18
 
19
+ def init_ui(self):
20
  try:
21
+ uic.loadUi(self.settings.get_ui_dir_path() + '/multitargeting_window.ui', self)
22
+ self._init_ui_components()
 
23
  except Exception as e:
24
+ show_error(self.settings, "Error initializing MultitargetingWindowView", str(e))
25
 
26
+ def _init_ui_components(self):
27
+ self._init_grpSelectOrganism()
28
+ self._init_grpSeedAnalysis()
29
+ self._init_grpGlobalAnalysis()
 
30
 
31
+ def _init_grpSelectOrganism(self):
32
+ self.combo_box_organism = self._find_widget('cmbOrganism', QtWidgets.QComboBox)
33
+ self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
34
+ self.push_button_analyze = self._find_widget('pbtnAnalyze', QtWidgets.QPushButton)
35
+ self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
36
+ self.tool_button_sql_settings = self._find_widget('tbtnSQLSettings', QtWidgets.QToolButton)
37
+ self.table_seeds = self._find_widget('tblSeeds', QtWidgets.QTableWidget)
38
 
39
+ self.table_seeds.setColumnCount(8)
40
+ self.table_seeds.setHorizontalHeaderLabels(["Seed", "Total Repeats", "Avg. Repeats/Scaffold", "Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"])
41
+ self.table_seeds.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
42
+ self.table_seeds.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
43
+ self.table_seeds.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
44
+ self.table_seeds.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeMode.Stretch)
45
 
46
+ def _init_grpSeedAnalysis(self):
47
+ # Get the tab widget
48
+ self.tab_widget_seed_analysis = self._find_widget('tabsSeedAnalysis', QtWidgets.QTabWidget)
49
 
50
+ # Get screen height for scaling
51
+ screen = self.screen()
52
+ height = screen.geometry().height()
53
+ # Set tab widget to take up less vertical space (e.g., 30% of screen height)
54
+ tab_height = int((height * 30) / 100)
55
+ self.tab_widget_seed_analysis.setMinimumHeight(tab_height)
56
+ self.tab_widget_seed_analysis.setMaximumHeight(tab_height)
57
 
58
+ # Initialize other widgets
59
+ self.tab_chromosome_viewer = self._find_widget('tabChromosomeViewer', QtWidgets.QWidget)
60
+ self.graphical_view_chromosome = self._find_widget('graphviewChromosome', QtWidgets.QGraphicsView)
61
+ self.scroll_chromosome = self._find_widget('scrollChromosome', QtWidgets.QScrollArea)
62
+
63
+ # Create a widget to hold the chromosome visualizations
64
+ self.chromosome_content_widget = QtWidgets.QWidget()
65
+ self.chromosome_layout = QtWidgets.QVBoxLayout(self.chromosome_content_widget)
66
+ self.scroll_chromosome.setWidget(self.chromosome_content_widget)
67
+ self.scroll_chromosome.setWidgetResizable(True)
68
 
69
+ self.tab_seed_distribution = self._find_widget('tabSeedDistribution', QtWidgets.QWidget)
70
+ self.plot_repeat_vs_chromosome = self._find_widget('plotRepeatVsChromosome', QtWidgets.QWidget)
 
71
 
72
+ # Initialize scene for chromosome details
73
+ self.scene = QtWidgets.QGraphicsScene()
74
+ self.scene2 = QtWidgets.QGraphicsScene()
75
+ self.graphical_view_chromosome.setScene(self.scene2)
76
+
77
+ # Set up event filters
78
+ self.scroll_chromosome.viewport().installEventFilter(self)
79
+ self.graphical_view_chromosome.viewport().installEventFilter(self)
80
 
81
+ # Dictionary to map canvases to chromosomes
82
+ self.canvas_chromosome_map = {}
83
 
84
+ def _init_grpGlobalAnalysis(self):
85
+ self.push_button_statistics_overview = self._find_widget('pbtnStatisticsOverview', QtWidgets.QPushButton)
86
+
87
+ self.tab_repeats_vs_seed = self._find_widget('tabRepeatsVsSeed', QtWidgets.QWidget)
88
+ self.plot_repeats_vs_seed = self._find_widget('plotRepeatsVsSeed', QtWidgets.QWidget)
89
+
90
+ self.tab_sequences_vs_repeats = self._find_widget('tabSequencesVsRepeats', QtWidgets.QWidget)
91
+ self.plot_sequences_vs_repeats = self._find_widget('plotSequencesVsRepeats', QtWidgets.QWidget)
92
+
93
+ def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
94
+ widget = self.findChild(widget_type, name)
95
+ if widget is None:
96
+ self.logger.warning(f"Widget '{name}' not found in UI file.")
97
+ return widget
98
+
99
+ def update_seeds_table(self, data):
100
+ """Update the seeds table with the provided data"""
101
+ self.table_seeds.setRowCount(len(data))
102
+ for row, row_data in enumerate(data):
103
+ # Unpack the data
104
+ (seed, total_count, avg_per_scaffold, sequences,
105
+ consensus_percent, score, pam, strand) = row_data
106
+
107
+ # Set data in table cells
108
+ self.table_seeds.setItem(row, 0, QTableWidgetItem(str(seed)))
109
+ self.table_seeds.setItem(row, 1, QTableWidgetItem(str(total_count)))
110
+ self.table_seeds.setItem(row, 2, QTableWidgetItem(f"{avg_per_scaffold:.2f}"))
111
+ self.table_seeds.setItem(row, 3, QTableWidgetItem(sequences.split(',')[0])) # First sequence
112
+ self.table_seeds.setItem(row, 4, QTableWidgetItem(f"{consensus_percent:.1f}"))
113
+ self.table_seeds.setItem(row, 5, QTableWidgetItem(str(score)))
114
+ self.table_seeds.setItem(row, 6, QTableWidgetItem(str(pam)))
115
+ self.table_seeds.setItem(row, 7, QTableWidgetItem(str(strand)))
116
+
117
+ # Set alignment for all cells
118
+ for col in range(8):
119
+ item = self.table_seeds.item(row, col)
120
+ if item:
121
+ item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
122
+
123
+ self.table_seeds.resizeColumnsToContents()
124
+
125
+ def setup_plots(self):
126
+ """Initialize the matplotlib plots"""
127
+ self.repeats_vs_seed_canvas = MplCanvas(self, width=8, height=6)
128
+ self.sequences_vs_repeats_canvas = MplCanvas(self, width=8, height=6)
129
+ self.repeat_vs_chromosome_canvas = MplCanvas(self, width=8, height=6)
130
+
131
+ # Add canvases to their respective layouts without toolbars
132
+ for plot_widget, canvas in [
133
+ (self.plot_repeats_vs_seed, self.repeats_vs_seed_canvas),
134
+ (self.plot_sequences_vs_repeats, self.sequences_vs_repeats_canvas),
135
+ (self.plot_repeat_vs_chromosome, self.repeat_vs_chromosome_canvas)
136
+ ]:
137
+ layout = QtWidgets.QVBoxLayout(plot_widget)
138
+ layout.setContentsMargins(0, 0, 0, 0) # Reduce margins
139
+ layout.addWidget(canvas)
140
+
141
+ def update_plots(self, repeats_data, sequences_data, chromosome_data):
142
+ """Update all plots with new data"""
143
+ self._update_repeats_vs_seed_plot(repeats_data)
144
+ self._update_sequences_vs_repeats_plot(sequences_data)
145
+ self._update_repeat_vs_chromosome_plot(chromosome_data)
146
+
147
+ def _update_repeats_vs_seed_plot(self, data):
148
+ """Update the repeats vs seed line plot"""
149
  try:
150
+ self.logger.debug("Starting repeats vs seed plot update")
151
+ print(f"data: {data}")
152
+
153
+ self.repeats_vs_seed_canvas.axes.clear()
154
+
155
+ if data and 'counts' in data:
156
+ y1 = data['counts']
157
+ x = range(len(y1))
158
+
159
+ # Plot with larger markers and line width for better visibility
160
+ self.repeats_vs_seed_canvas.axes.plot(x, y1, linewidth=1.5, marker='.', markersize=3)
161
+
162
+ # Set labels and title with larger font sizes
163
+ self.repeats_vs_seed_canvas.axes.set_xlabel('Seed ID Number', fontsize=12)
164
+ self.repeats_vs_seed_canvas.axes.set_ylabel('Number of Repeats', fontsize=12)
165
+ self.repeats_vs_seed_canvas.axes.set_title('Number of Repeats per Seed ID Number', fontsize=14)
166
+
167
+ # Set tick label size
168
+ self.repeats_vs_seed_canvas.axes.tick_params(axis='both', which='major', labelsize=10)
169
+
170
+ # Add grid for better readability
171
+ self.repeats_vs_seed_canvas.axes.grid(True, linestyle='--', alpha=0.7)
172
+
173
+ # Store statistics if needed
174
+ if 'stats' in data:
175
+ self.average = data['stats']['average']
176
+ self.mode = data['stats']['mode']
177
+ self.median = data['stats']['median']
178
+ self.repeat_count = data['stats']['repeat_count']
179
+
180
+ # Force draw
181
+ self.repeats_vs_seed_canvas.draw()
182
+
183
+ else:
184
+ self.logger.warning("No valid data for plotting")
185
+ self.repeats_vs_seed_canvas.draw() # Still need to draw even when clearing
186
+
187
  except Exception as e:
188
+ self.logger.error(f"Error updating repeats vs seed plot: {str(e)}")
189
 
190
+ def _update_sequences_vs_repeats_plot(self, data):
191
+ """Update the sequences vs repeats plot"""
192
  try:
193
+ self.sequences_vs_repeats_canvas.axes.clear()
194
+
195
+ if data and 'x_vals' in data and 'y_vals' in data:
196
+ x = data['x_vals']
197
+ y = data['y_vals']
198
+
199
+ # Create scatter plot with specific style
200
+ self.sequences_vs_repeats_canvas.axes.scatter(x, y, s=15, color='blue')
201
+
202
+ # Set y-axis to log scale
203
+ self.sequences_vs_repeats_canvas.axes.set_yscale('log')
204
+
205
+ # Set labels and title
206
+ self.sequences_vs_repeats_canvas.axes.set_xlabel('Number of Repeats', fontsize=12)
207
+ self.sequences_vs_repeats_canvas.axes.set_ylabel('Number of Sequences', fontsize=12)
208
+ self.sequences_vs_repeats_canvas.axes.set_title('Number of Sequences per Number of Repeats', fontsize=14)
209
+
210
+ # Set tick label size
211
+ self.sequences_vs_repeats_canvas.axes.tick_params(axis='both', which='major', labelsize=10)
212
+
213
+ # Add grid for better readability
214
+ self.sequences_vs_repeats_canvas.axes.grid(True, linestyle='--', alpha=0.7)
215
+
216
+ # Force integer ticks on x-axis
217
+ self.sequences_vs_repeats_canvas.axes.xaxis.set_major_locator(MaxNLocator(integer=True))
218
+
219
+ # Set axis ranges to match the image
220
+ self.sequences_vs_repeats_canvas.axes.set_xlim(0, max(x) + 5) # Add some padding
221
+ self.sequences_vs_repeats_canvas.axes.set_ylim(1, 10**4) # Log scale from 1 to 10^4
222
+
223
+ self.sequences_vs_repeats_canvas.draw()
224
+
225
  except Exception as e:
226
+ self.logger.error(f"Error updating sequences vs repeats plot: {str(e)}")
227
 
228
+ def _update_repeat_vs_chromosome_plot(self, data):
229
+ """Update the chromosome bar plot"""
230
  try:
231
+ self.repeat_vs_chromosome_canvas.axes.clear()
232
+
233
+ if data:
234
+ y = []
235
+ x_labels = []
236
+
237
+ # Get sorted chromosome numbers and their counts
238
+ for chromo in sorted(data.keys()):
239
+ x_labels.append(chromo)
240
+ y.append(data[chromo])
241
+
242
+ x = list(range(0, len(x_labels)))
243
+
244
+ # Create the bar plot
245
+ self.repeat_vs_chromosome_canvas.axes.bar(x, y, align='center')
246
+
247
+ # Set integer y-axis
248
+ self.repeat_vs_chromosome_canvas.axes.yaxis.set_major_locator(MaxNLocator(integer=True))
249
+
250
+ # Set y-axis limits
251
+ self.repeat_vs_chromosome_canvas.axes.set_ylim(0, max(y) + 1)
252
+
253
+ # Set x-axis ticks and labels
254
+ self.repeat_vs_chromosome_canvas.axes.set_xticks(x)
255
+ self.repeat_vs_chromosome_canvas.axes.set_xticklabels(x_labels)
256
+
257
+ # If many chromosomes, show only some labels
258
+ if len(x_labels) > 10:
259
+ tick_spacing = round(len(x_labels)/10)
260
+ for i, t in enumerate(self.repeat_vs_chromosome_canvas.axes.get_xticklabels()):
261
+ if (i % tick_spacing) != 0:
262
+ t.set_visible(False)
263
+
264
+ # Set labels and title
265
+ self.repeat_vs_chromosome_canvas.axes.set_xlabel('Chromosome', fontsize=10)
266
+ self.repeat_vs_chromosome_canvas.axes.set_ylabel('Number of Repeats', fontsize=10)
267
+ self.repeat_vs_chromosome_canvas.axes.set_title('Repeats per Chromosome', fontsize=10)
268
+
269
+ # Set tick label size
270
+ self.repeat_vs_chromosome_canvas.axes.tick_params(axis='both', which='major', labelsize=8)
271
+
272
+ self.repeat_vs_chromosome_canvas.draw()
273
+
274
  except Exception as e:
275
+ self.logger.error(f"Error updating repeat vs chromosome plot: {str(e)}")
276
 
277
+ def fill_chromosome_viewer(self, seed_data, event_data):
278
+ """Fill the chromosome viewer with visualization"""
279
  try:
280
+ # Clear out old widgets in layout
281
+ for i in reversed(range(self.chromosome_layout.count())):
282
+ self.chromosome_layout.itemAt(i).widget().setParent(None)
283
+
284
+ # Get sorted list of chromosomes
285
+ chromo_keys = sorted(list(seed_data.keys()))
286
+
287
+ # Get screen height for scaling
288
+ screen = self.screen()
289
+ height = screen.geometry().height()
290
+ groupbox_height = int((height * 100) / 1080)
291
+
292
+ # Create visualization for each chromosome
293
+ for chromo in chromo_keys:
294
+ group_box = QtWidgets.QGroupBox()
295
+ group_box.setTitle(f"Chromosome {chromo}")
296
+ group_box.setMinimumHeight(groupbox_height)
297
+ group_box.setMaximumHeight(groupbox_height)
298
+ layout = QtWidgets.QVBoxLayout(group_box)
299
+
300
+ # Create canvas for this chromosome
301
+ canvas = MplCanvas()
302
+ canvas.axes.eventplot(seed_data[chromo])
303
+ canvas.mpl_connect("motion_notify_event", self._chromosome_event_handler)
304
+
305
+ # Add border lines
306
+ canvas.axes.hlines(1.5, -0.01, 1.01, colors="Black", linewidth=1.5)
307
+ canvas.axes.hlines(0.5, -0.01, 1.01, colors="Black", linewidth=1.5)
308
+ canvas.axes.vlines(-0.01, 0.5, 1.5, colors="Black", linewidth=1.5)
309
+ canvas.axes.vlines(1.01, 0.5, 1.5, colors="Black", linewidth=1.5)
310
+
311
+ # Set axis limits
312
+ canvas.axes.set_ylim(0.45, 1.55)
313
+ canvas.axes.set_xlim(-0.05, 1.05)
314
+ canvas.axes.axis('off')
315
+ canvas.draw()
316
+
317
+ # Store canvas mapping
318
+ self.canvas_chromosome_map[canvas] = chromo
319
+ self.event_data = event_data # Store event data for hover details
320
+
321
+ layout.addWidget(canvas)
322
+ self.chromosome_layout.addWidget(group_box)
323
+
324
+ except Exception as e:
325
+ show_error(self.settings, "Error in fill_chromosome_viewer", str(e))
326
+
327
+ def _chromosome_event_handler(self, event):
328
+ """Handle mouse events on chromosome visualization"""
329
+ try:
330
+ # Get current mouse location
331
+ x = event.xdata
332
+ y = event.y
333
+ if x is None: # Mouse outside the plot
334
+ return
335
+
336
+ # Get event data relative to the canvas
337
+ curr_chromosome = self.canvas_chromosome_map[event.canvas]
338
+ chromosome_seed_data = self.event_data[curr_chromosome]
339
+
340
+ # Get targets within range of mouse location
341
+ local_targets = []
342
+ for entry in chromosome_seed_data:
343
+ try:
344
+ if abs(x - entry[0]) <= 0.001:
345
+ local_targets.append(entry)
346
+ except:
347
+ pass
348
+
349
+ # Update viewer with target details if found
350
+ if local_targets:
351
+ self.scene2 = QtWidgets.QGraphicsScene()
352
+ self.graphical_view_chromosome.setScene(self.scene2)
353
+
354
+ output = ""
355
+ for target in local_targets:
356
+ output += f"Location: {target[1]} | Seq: {target[2]} | PAM: {target[3]} | SCR: {target[4]} | DIRA: {target[5]}\n"
357
+
358
+ text = self.scene2.addText(output)
359
+ font = QtGui.QFont()
360
+ font.setPointSize(self.settings.fontSize if hasattr(self.settings, 'fontSize') else 12)
361
+ text.setFont(font)
362
+
363
  except Exception as e:
364
+ self.logger.error(f"Error in chromosome event handler: {str(e)}")
365
+
366
+ class MplCanvas(FigureCanvasQTAgg):
367
+ def __init__(self, parent=None, width=8, height=6, dpi=100):
368
+ fig = Figure(figsize=(width, height), dpi=dpi, tight_layout=True)
369
+ self.axes = fig.add_subplot(111)
370
+ super(MplCanvas, self).__init__(fig)
src/views/NCBIRenameWindowView.py CHANGED
@@ -5,7 +5,8 @@ class NCBIRenameWindowView(QtWidgets.QDialog):
5
  def __init__(self, settings, files):
6
  super(NCBIRenameWindowView, self).__init__()
7
  self.settings = settings
8
- self.files = files
 
9
  self.setup_ui()
10
 
11
  def setup_ui(self):
@@ -42,6 +43,7 @@ class NCBIRenameWindowView(QtWidgets.QDialog):
42
  def populate_table(self):
43
  self.rename_table.setRowCount(len(self.files))
44
  for row, file in enumerate(self.files):
 
45
  item = QtWidgets.QTableWidgetItem(file)
46
  item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
47
  self.rename_table.setItem(row, 0, item)
 
5
  def __init__(self, settings, files):
6
  super(NCBIRenameWindowView, self).__init__()
7
  self.settings = settings
8
+ # Store only filenames, not full paths
9
+ self.files = [os.path.basename(file) for file in files]
10
  self.setup_ui()
11
 
12
  def setup_ui(self):
 
43
  def populate_table(self):
44
  self.rename_table.setRowCount(len(self.files))
45
  for row, file in enumerate(self.files):
46
+ # Create item with just the filename
47
  item = QtWidgets.QTableWidgetItem(file)
48
  item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
49
  self.rename_table.setItem(row, 0, item)
src/views/NCBIWindowView.py CHANGED
@@ -4,76 +4,110 @@ import os
4
  from typing import Optional
5
 
6
  class NCBIWindowView(QtWidgets.QMainWindow):
 
 
7
  def __init__(self, settings):
8
  super(NCBIWindowView, self).__init__()
9
  self.settings = settings
10
  self.logger = settings.get_logger()
11
  self.progress_bars = {}
12
  self.progress_labels = {}
13
-
14
- self.setup_ui()
15
-
16
- def setup_ui(self):
17
- uic.loadUi(os.path.join(self.settings.get_ui_dir_path(), "ncbi_window_v2.ui"), self)
18
-
19
- self._init_ui_components()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  def _init_ui_components(self) -> None:
22
- self._init_grpStep1()
23
- self._init_grpStep2()
24
- self._init_grpStep3()
 
 
 
 
 
25
 
26
  def _init_grpStep1(self) -> None:
27
- self.line_edit_organism = self._find_widget("ledOrganism", QtWidgets.QLineEdit)
28
- self.line_edit_strain = self._find_widget("ledStrain", QtWidgets.QLineEdit)
29
- self.line_edit_max_results = self._find_widget("ledMaxResults", QtWidgets.QLineEdit)
30
- self.check_box_complete_genomes_only = self._find_widget("chkCompleteGenomesOnly", QtWidgets.QCheckBox)
 
 
 
 
 
 
 
31
 
32
  def _init_grpStep2(self) -> None:
33
- self.push_button_search = self._find_widget("pbtnSearch", QtWidgets.QPushButton)
34
- self.check_box_select_all_rows = self._find_widget("chkSelectAllRows", QtWidgets.QCheckBox)
35
- self.table_ncbi_results = self._find_widget("tblNCBIResults", QtWidgets.QTableView)
 
 
 
36
 
37
  def _init_grpStep3(self) -> None:
38
- self.radio_button_collections_refseq = self._find_widget("rbtnCollectionsRefSeq", QtWidgets.QRadioButton)
39
- self.radio_button_collections_genbank = self._find_widget("rbtnCollectionsGenBank", QtWidgets.QRadioButton)
40
-
41
- self.check_box_file_types_fna = self._find_widget("chkFileTypesFNA", QtWidgets.QCheckBox)
42
- self.check_box_file_types_gbff = self._find_widget("chkFileTypesGBFF", QtWidgets.QCheckBox)
43
-
44
- self.push_button_download_files = self._find_widget("pbtnDownloadFiles", QtWidgets.QPushButton)
45
-
46
- self.progress_bar_download_files = self._find_widget("pbDownloadFiles", QtWidgets.QProgressBar)
47
- self.label_download_files_status = self._find_widget("lblDownloadFilesStatus", QtWidgets.QLabel)
48
-
49
- self.progress_bar_download_files.setValue(0)
50
-
51
- def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
52
- widget = self.findChild(widget_type, name)
53
- if widget is None:
54
- self.settings.logger.warning(f"Widget '{name}' not found in UI file.")
55
- return widget
56
-
57
- def set_styles(self):
58
- groupbox_style = """
59
- QGroupBox:title{subcontrol-origin: margin;
60
- left: 10px;
61
- padding: 0 5px 0 5px;}
62
- QGroupBox#Step1{border: 2px solid rgb(111,181,110);
63
- border-radius: 9px;
64
- font: bold 14pt 'Arial';
65
- margin-top: 10px;}"""
66
- self.Step1.setStyleSheet(groupbox_style)
67
- self.Step2.setStyleSheet(groupbox_style.replace("Step1","Step2"))
68
- self.Step3.setStyleSheet(groupbox_style.replace("Step1","Step3"))
69
 
70
  def populate_ncbi_table(self, model):
 
71
  self.table_ncbi_results.setModel(model)
72
 
73
  # Set selection behavior to select entire rows
74
  self.table_ncbi_results.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
75
  self.table_ncbi_results.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.MultiSelection)
76
 
 
 
 
 
 
 
77
  # Set the horizontal header to resize mode
78
  header = self.table_ncbi_results.horizontalHeader()
79
  header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.Interactive)
@@ -104,6 +138,9 @@ class NCBIWindowView(QtWidgets.QMainWindow):
104
 
105
  # Ensure the last column doesn't stretch
106
  header.setStretchLastSection(False)
 
 
 
107
 
108
  def get_search_parameters(self):
109
  return {
@@ -145,3 +182,8 @@ class NCBIWindowView(QtWidgets.QMainWindow):
145
  if source_model:
146
  source_model.clear()
147
  self.table_ncbi_results.reset()
 
 
 
 
 
 
4
  from typing import Optional
5
 
6
  class NCBIWindowView(QtWidgets.QMainWindow):
7
+ initialization_complete = QtCore.pyqtSignal() # New signal
8
+
9
  def __init__(self, settings):
10
  super(NCBIWindowView, self).__init__()
11
  self.settings = settings
12
  self.logger = settings.get_logger()
13
  self.progress_bars = {}
14
  self.progress_labels = {}
15
+ self._is_initialized = False # Track initialization state
16
+ self._setup_basic_ui() # Only do basic initialization first
17
+
18
+ def _setup_basic_ui(self):
19
+ """Initial minimal setup to show the window quickly"""
20
+ try:
21
+ uic.loadUi(os.path.join(self.settings.get_ui_dir_path(), "ncbi_window_v2.ui"), self)
22
+
23
+ QtCore.QTimer.singleShot(100, self._complete_initialization)
24
+
25
+ except Exception as e:
26
+ self.logger.error(f"Error in basic UI setup: {str(e)}")
27
+ raise
28
+
29
+ def _complete_initialization(self):
30
+ """Complete the full initialization of UI components"""
31
+ try:
32
+ if self._is_initialized:
33
+ return
34
+
35
+ # Initialize all UI components
36
+ self._init_ui_components()
37
+
38
+ self._is_initialized = True
39
+ self.logger.debug("NCBI Window initialization completed")
40
+
41
+ # Emit signal after everything is initialized
42
+ self.initialization_complete.emit()
43
+
44
+ except Exception as e:
45
+ self.logger.error(f"Error in complete initialization: {str(e)}")
46
+ raise
47
 
48
  def _init_ui_components(self) -> None:
49
+ """Initialize all UI components at once instead of using timers"""
50
+ try:
51
+ self._init_grpStep1()
52
+ self._init_grpStep2()
53
+ self._init_grpStep3()
54
+ except Exception as e:
55
+ self.logger.error(f"Error in _init_ui_components: {str(e)}")
56
+ raise
57
 
58
  def _init_grpStep1(self) -> None:
59
+ try:
60
+ self.line_edit_organism = self._find_widget("ledOrganism", QtWidgets.QLineEdit)
61
+ self.line_edit_strain = self._find_widget("ledStrain", QtWidgets.QLineEdit)
62
+ self.line_edit_max_results = self._find_widget("ledMaxResults", QtWidgets.QLineEdit)
63
+ self.check_box_complete_genomes_only = self._find_widget("chkCompleteGenomesOnly", QtWidgets.QCheckBox)
64
+
65
+ # Set default values
66
+ self.line_edit_max_results.setText("100")
67
+
68
+ except Exception as e:
69
+ self.logger.error(f"Error initializing Step 1: {str(e)}")
70
 
71
  def _init_grpStep2(self) -> None:
72
+ try:
73
+ self.push_button_search = self._find_widget("pbtnSearch", QtWidgets.QPushButton)
74
+ self.check_box_select_all_rows = self._find_widget("chkSelectAllRows", QtWidgets.QCheckBox)
75
+ self.table_ncbi_results = self._find_widget("tblNCBIResults", QtWidgets.QTableView)
76
+ except Exception as e:
77
+ self.logger.error(f"Error initializing Step 2: {str(e)}")
78
 
79
  def _init_grpStep3(self) -> None:
80
+ try:
81
+ self.radio_button_collections_refseq = self._find_widget("rbtnCollectionsRefSeq", QtWidgets.QRadioButton)
82
+ self.radio_button_collections_genbank = self._find_widget("rbtnCollectionsGenBank", QtWidgets.QRadioButton)
83
+ self.check_box_file_types_fna = self._find_widget("chkFileTypesFNA", QtWidgets.QCheckBox)
84
+ self.check_box_file_types_gbff = self._find_widget("chkFileTypesGBFF", QtWidgets.QCheckBox)
85
+ self.push_button_download_files = self._find_widget("pbtnDownloadFiles", QtWidgets.QPushButton)
86
+ self.progress_bar_download_files = self._find_widget("pbDownloadFiles", QtWidgets.QProgressBar)
87
+ self.label_download_files_status = self._find_widget("lblDownloadFilesStatus", QtWidgets.QLabel)
88
+
89
+ # Set initial states
90
+ self.progress_bar_download_files.setValue(0)
91
+ self.radio_button_collections_refseq.setChecked(True)
92
+ self.check_box_file_types_fna.setChecked(True)
93
+
94
+ except Exception as e:
95
+ self.logger.error(f"Error initializing Step 3: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  def populate_ncbi_table(self, model):
98
+ """Populate the table with data and set up proper row selection"""
99
  self.table_ncbi_results.setModel(model)
100
 
101
  # Set selection behavior to select entire rows
102
  self.table_ncbi_results.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
103
  self.table_ncbi_results.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.MultiSelection)
104
 
105
+ # Disable cell editing
106
+ self.table_ncbi_results.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
107
+
108
+ # Enable sorting
109
+ self.table_ncbi_results.setSortingEnabled(True)
110
+
111
  # Set the horizontal header to resize mode
112
  header = self.table_ncbi_results.horizontalHeader()
113
  header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.Interactive)
 
138
 
139
  # Ensure the last column doesn't stretch
140
  header.setStretchLastSection(False)
141
+
142
+ # Set focus policy to enable keyboard selection
143
+ self.table_ncbi_results.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
144
 
145
  def get_search_parameters(self):
146
  return {
 
182
  if source_model:
183
  source_model.clear()
184
  self.table_ncbi_results.reset()
185
+ def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
186
+ widget = self.findChild(widget_type, name)
187
+ if widget is None:
188
+ self.settings.logger.warning(f"Widget '{name}' not found in UI file.")
189
+ return widget
src/views/NewEndonuclease.py DELETED
@@ -1,228 +0,0 @@
1
- import sys, os
2
- from PyQt5 import QtWidgets, uic, QtGui, QtCore, Qt
3
- import models.GlobalSettings as GlobalSettings
4
- from PyQt5.QtGui import QIntValidator
5
- import traceback
6
- import math
7
- from utils.ui import show_message, show_error, scale_ui, center_ui
8
-
9
- logger = GlobalSettings.logger
10
-
11
- class NewEndonuclease(QtWidgets.QMainWindow):
12
- def __init__(self):
13
- print("Initializing NewEndonuclease class")
14
- try:
15
- super(NewEndonuclease, self).__init__()
16
- uic.loadUi(GlobalSettings.appdir + 'ui/newendonuclease.ui', self)
17
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
18
- self.setWindowTitle('New Endonuclease')
19
- self.error = False
20
- pamFlag = False
21
-
22
- self.onList = []
23
- self.offList = []
24
-
25
- self.onList, self.offList = self.get_on_off_data() ### Call function to fill on- and off- data name lists
26
-
27
- for name in self.onList: ### Add on-target names to drop-down
28
- self.comboBox.addItem(str(name))
29
-
30
- for name in self.offList: ### Add off-target names to drop-down
31
- self.comboBox_2.addItem(str(name))
32
-
33
- self.submit_button.clicked.connect(self.submit)
34
- self.cancel_button.clicked.connect(self.cancel)
35
-
36
- ### Set up validators for input fields:
37
- reg_ex1 = QtCore.QRegExp("[^/\\\\_]+") # No slashes or underscores
38
- reg_ex2 = QtCore.QRegExp("[^/\\\\_\\s]+") # No slashes, underscores, or spaces
39
- reg_ex3 = QtCore.QRegExp("[acdefghiklmnpqrstvwyACDEFGHIKLMNPQRSTVWY\S]+") # Only approved PAM characters and no spaces
40
- input_validator1 = QtGui.QRegExpValidator(reg_ex1, self)
41
- input_validator2 = QtGui.QRegExpValidator(reg_ex2, self)
42
- input_validator3 = QtGui.QRegExpValidator(reg_ex3, self)
43
- self.organism_name.setValidator(input_validator1)
44
- self.abbreviation.setValidator(input_validator2)
45
- self.pam_sequence.setValidator(input_validator3)
46
-
47
- self.seed_length.setValidator(QIntValidator(0,30,self.seed_length))
48
- self.five_length.setValidator(QIntValidator(0,20,self.five_length))
49
- self.three_length.setValidator(QIntValidator(0,20,self.three_length))
50
-
51
- groupbox_style = """
52
- QGroupBox:title{subcontrol-origin: margin;
53
- left: 10px;
54
- padding: 0 5px 0 5px;}
55
- QGroupBox#groupBox{border: 2px solid rgb(111,181,110);
56
- border-radius: 9px;
57
- font: bold 14pt 'Arial';
58
- margin-top: 10px;}"""
59
-
60
- self.groupBox.setStyleSheet(groupbox_style)
61
- self.groupBox_2.setStyleSheet(groupbox_style.replace("groupBox","groupBox_2"))
62
- self.groupBox_3.setStyleSheet(groupbox_style.replace("groupBox","groupBox_3"))
63
-
64
- scale_ui(self, custom_scale_width=480, custom_scale_height=615)
65
- except Exception as e:
66
- show_error("Error initializing NewEndonuclease class.", e)
67
-
68
- #helper function for writing new endo information to CASPERinfo - used by submit()
69
- def writeNewEndonuclease(self, newEndonucleaseStr):
70
- try:
71
- with open(GlobalSettings.appdir + 'CASPERinfo', 'r') as f, open(GlobalSettings.appdir + "new_file", 'w+') as f1:
72
- for line in f:
73
- f1.write(line)
74
- if 'ENDONUCLEASES' in line:
75
- f1.write(newEndonucleaseStr + '\n') # Move f1.write(line) above, to write above instead
76
- os.remove(GlobalSettings.appdir + "CASPERinfo")
77
- os.rename(GlobalSettings.appdir + "new_file",
78
- GlobalSettings.appdir + "CASPERinfo") # Rename the new file
79
- except Exception as e:
80
- show_error("Error in writeNewEndonuclease() in New Endonuclease.", e)
81
-
82
- #submit new endo to CASPERinfo file
83
- def submit(self):
84
- try:
85
- # This is executed when the button is pressed
86
- name = str(self.organism_name.text())
87
- abbr = str(self.abbreviation.text())
88
- crisprtype = str(self.crispr_type.text())
89
- seed_len = str(self.seed_length.text())
90
- five_len = str(self.five_length.text())
91
- three_len = str(self.three_length.text())
92
- pam = str(self.pam_sequence.text()).upper()
93
- ### Check for multiple PAMs and format if present
94
- if len(pam.split(','))>0:
95
- pam = [x.strip() for x in pam.split(',')]
96
- pam = ",".join(pam)
97
- ### Check for PAM directionality
98
- if self.five_pam.isChecked():
99
- pam_dir = str(5)
100
- else:
101
- pam_dir = str(3)
102
- on_scoring = str(self.comboBox.currentText())
103
- off_scoring = str(self.comboBox_2.currentText())
104
- length = len(seed_len) + len(five_len) + len(three_len)
105
- argument_list = [abbr, pam, five_len, seed_len, three_len, pam_dir, name, crisprtype, on_scoring, off_scoring]
106
- validPAM = ('A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'Y')
107
- self.error = False;
108
-
109
- ### Error checking for PAM alphabet
110
- for letter in pam:
111
- if (letter not in validPAM):
112
- show_message(
113
- fontSize=12,
114
- icon=QtWidgets.QMessageBox.Icon.Critical,
115
- title="Invalid PAM",
116
- message="Invalid characters in PAM Sequence."
117
- )
118
- return True
119
- ### Error checking for filling out all fields
120
- for arg in argument_list:
121
- if ';' in arg:
122
- show_message(
123
- fontSize=12,
124
- icon=QtWidgets.QMessageBox.Icon.Critical,
125
- title="Invalid Semicolon",
126
- message="Invalid character used: ';'."
127
- )
128
- return True
129
- elif arg == "":
130
- show_message(
131
- fontSize=12,
132
- icon=QtWidgets.QMessageBox.Icon.Critical,
133
- title="Empty Field",
134
- message="Please fill in all fields."
135
- )
136
- return True
137
- else:
138
- pass
139
-
140
- ### Check for duplicate endo abbreviations
141
- for key in GlobalSettings.mainWindow.organisms_to_endos:
142
- endo = GlobalSettings.mainWindow.organisms_to_endos[key]
143
- if abbr in endo:
144
- show_message(
145
- fontSize=12,
146
- icon=QtWidgets.QMessageBox.Icon.Critical,
147
- title="Duplicate endo name.",
148
- message="The given abbreviation already exists. Please choose a unique identifier."
149
- )
150
- return True
151
- else:
152
- pass
153
-
154
- myString = ""
155
- for i, arg in enumerate(argument_list):
156
- if i == len(argument_list)-1: ### Last argument in list
157
- myString += str(arg)
158
- else:
159
- myString += str(arg) + ";"
160
-
161
- self.writeNewEndonuclease(myString)
162
-
163
- ### Refresh endonuclease dropdown in New Genome
164
- GlobalSettings.mainWindow.newGenome.fillEndo()
165
-
166
- self.clear_all()
167
- self.close()
168
- except Exception as e:
169
- show_error("Error in submit() in New Endonuclease.", e)
170
-
171
- #cancel and close window
172
- def cancel(self):
173
- try:
174
- self.clear_all()
175
- self.close()
176
- except Exception as e:
177
- show_error("Error in cancel() in New Endonuclease.", e)
178
-
179
- # This function clears all of the line edits
180
- def clear_all(self):
181
- try:
182
- self.organism_name.clear()
183
- self.abbreviation.clear()
184
- self.crispr_type.clear()
185
- self.seed_length.clear()
186
- self.five_length.clear()
187
- self.three_length.clear()
188
- self.pam_sequence.clear()
189
- except Exception as e:
190
- show_error("Error in clear_all() in New Endonuclease.", e)
191
-
192
- # This function parses CASPERinfo to return the names (in lists) of all on-target and off-target scoring data
193
- def get_on_off_data(self):
194
- try:
195
- filename = GlobalSettings.appdir + "CASPERinfo"
196
- retList_on = []
197
- retList_off = []
198
- with open(filename, 'r') as f:
199
- lines = f.readlines()
200
- for i, line in enumerate(lines):
201
- line = str(line)
202
- if "ON-TARGET DATA" in line:
203
- index = i
204
- while "-----" not in line:
205
- if "DATA:" in line:
206
- retList_on.append(line.split("DATA:")[-1].strip()) ### Append name of scoring data to on-target name list
207
- line = lines[index+1]
208
- index += 1
209
- else:
210
- line = lines[index+1]
211
- index += 1
212
- continue
213
- elif "OFF-TARGET MATRICES" in line:
214
- index = i
215
- while "-----" not in line:
216
- if "MATRIX:" in line:
217
- retList_off.append(line.split("MATRIX:")[-1].strip()) ### Append name of scoring data to off-target name list
218
- line = lines[index+1]
219
- index += 1
220
- else:
221
- line = lines[index+1]
222
- index += 1
223
- continue
224
- else:
225
- continue
226
- return retList_on, retList_off
227
- except Exception as e:
228
- show_error("Error in get_on_off_data() in New Endonuclease.", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/NewGenome.py DELETED
@@ -1,705 +0,0 @@
1
- from ast import Global
2
- import os
3
- from PyQt5 import QtWidgets, uic, QtGui, QtCore, Qt
4
- import models.GlobalSettings as GlobalSettings
5
- from functools import partial
6
- from utils.Algorithms import SeqTranslate
7
- import webbrowser
8
- import platform
9
- import traceback
10
- import math
11
- from utils.ui import show_message, show_error, scale_ui, center_ui
12
- from utils.web import ncbi_page, repo_page
13
-
14
- logger = GlobalSettings.logger
15
-
16
- def iter_except(function, exception):
17
- """Works like builtin 2-argument `iter()`, but stops on `exception`."""
18
- try:
19
- while True:
20
- yield function()
21
- except exception:
22
- return
23
-
24
- #UI prompt for when the user has finished running jobs in new genome to allow them to choose where the want to proceed
25
- class goToPrompt(QtWidgets.QMainWindow):
26
- def __init__(self):
27
- try:
28
- super(goToPrompt, self).__init__()
29
- uic.loadUi(GlobalSettings.appdir + 'ui/newgenomenavigationpage.ui', self)
30
-
31
- groupbox_style = """
32
- QGroupBox:title{subcontrol-origin: margin;
33
- left: 10px;
34
- padding: 0 5px 0 5px;}
35
- QGroupBox#groupBox{border: 2px solid rgb(111,181,110);
36
- border-radius: 9px;
37
- font: bold 14pt 'Arial';
38
- margin-top: 10px;}"""
39
- self.groupBox.setStyleSheet(groupbox_style)
40
- scale_ui(self, custom_scale_width=575, custom_scale_height=175)
41
- self.setWindowTitle("New Genome")
42
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
43
- self.hide()
44
-
45
- except Exception as e:
46
- show_error("Unable to initialize goToPrompt class in New Genome.", e)
47
-
48
- #New genome class to allow users to generate new CSPR files
49
- class NewGenome(QtWidgets.QMainWindow):
50
- def __init__(self, info_path):
51
- try:
52
- super(NewGenome, self).__init__()
53
- uic.loadUi(GlobalSettings.appdir + 'ui/NewGenome.ui', self)
54
- self.setWindowTitle('New Genome')
55
- self.setWindowTitle('New Genome')
56
- self.info_path = info_path
57
-
58
- #---Style Modifications---#
59
-
60
- groupbox_style = """
61
- QGroupBox:title{subcontrol-origin: margin;
62
- left: 10px;
63
- padding: 0 5px 0 5px;}
64
- QGroupBox#Step1{border: 2px solid rgb(111,181,110);
65
- border-radius: 9px;
66
- font: bold 14pt 'Arial';
67
- margin-top: 10px;}"""
68
-
69
- self.Step1.setStyleSheet(groupbox_style)
70
- self.Step2.setStyleSheet(groupbox_style.replace("Step1","Step2"))
71
- self.Step3.setStyleSheet(groupbox_style.replace("Step1","Step3"))
72
-
73
- #---Button Modifications---#
74
-
75
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
76
- self.resetButton.clicked.connect(self.reset)
77
- self.submitButton.clicked.connect(self.submit)
78
- self.browseForFile.clicked.connect(self.selectFasta)
79
- self.remove_job.clicked.connect(self.remove_from_queue)
80
- self.output_browser.setText("Waiting for program initiation...")
81
- self.contButton.clicked.connect(self.continue_to_main)
82
-
83
- self.comboBoxEndo.currentIndexChanged.connect(self.endo_settings)
84
-
85
- self.runButton.clicked.connect(self.run_jobs_wrapper)
86
- self.clearButton.clicked.connect(self.clear_all)
87
-
88
- self.JobsQueue = [] # holds Job classes.
89
- self.check_strings = []
90
- self.Endos = dict()
91
- self.file = ""
92
-
93
- self.process = QtCore.QProcess()
94
- self.process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
95
- self.process.finished.connect(self.upon_process_finishing)
96
- self.seqTrans = SeqTranslate()
97
- self.exit = False
98
-
99
- self.first = False
100
- #show functionalities on window
101
- self.fillEndo()
102
-
103
- self.num_chromo_next = False
104
-
105
- #Jobs Table
106
- self.job_Table.setShowGrid(False)
107
- self.job_Table.horizontalHeader().setSectionsClickable(True)
108
- self.job_Table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
109
- self.job_Table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
110
- self.job_Table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
111
- self.job_Table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
112
- self.fin_index=0
113
-
114
- self.mwfg = self.frameGeometry() ##Center window
115
- self.cp = QtWidgets.QDesktopWidget().availableGeometry().center() ##Center window
116
- self.total_chrom_count = 0
117
- self.perc_increase = 0
118
- self.progress = 0
119
-
120
- #toolbar button actions
121
- self.visit_repo.triggered.connect(repo_page)
122
- self.go_ncbi.triggered.connect(ncbi_page)
123
-
124
- self.comboBoxEndo.currentIndexChanged.connect(self.changeEndos)
125
-
126
- ### NCBI tool
127
- self.NCBI_File_Search.clicked.connect(self.open_ncbi_tool)
128
-
129
- self.seed_length.setEnabled(False)
130
- self.five_length.setEnabled(False)
131
- self.three_length.setEnabled(False)
132
- self.repeats_box.setEnabled(False)
133
-
134
- ### User prompt class
135
- self.goToPrompt = goToPrompt()
136
- self.goToPrompt.goToMain.clicked.connect(self.continue_to_main)
137
- self.goToPrompt.goToMT.clicked.connect(self.continue_to_MT)
138
- self.goToPrompt.goToPop.clicked.connect(self.continue_to_pop)
139
-
140
- self.orgName.setFocus()
141
-
142
- ### Connect New endonuclease to New Genome
143
- self.actionUpload_New_Endonuclease.triggered.connect(self.launch_newEndonuclease)
144
-
145
- ### Set up validators for input fields:
146
- reg_ex1 = QtCore.QRegExp("[^/\\\\_]+") # No slashes or underscores
147
- reg_ex2 = QtCore.QRegExp("\\S+")
148
- input_validator1 = QtGui.QRegExpValidator(reg_ex1, self)
149
- input_validator2 = QtGui.QRegExpValidator(reg_ex2, self)
150
- self.orgName.setValidator(input_validator1)
151
- self.strainName.setValidator(input_validator1)
152
- self.orgCode.setValidator(input_validator2)
153
-
154
- scale_ui(self, custom_scale_width=850, custom_scale_height=750)
155
- self.first_show = True
156
- except Exception as e:
157
- show_error("Error initializing New Genome class.", e)
158
-
159
- def launch_newEndonuclease(self):
160
- try:
161
- GlobalSettings.mainWindow.getData()
162
- GlobalSettings.mainWindow.newEndonuclease.centerUI()
163
- GlobalSettings.mainWindow.newEndonuclease.show()
164
- GlobalSettings.mainWindow.newEndonuclease.activateWindow()
165
- except Exception as e:
166
- show_error("Error in launch_newEndonuclease() in New Genome.", e)
167
-
168
- #open the ncbi search tool window
169
- def open_ncbi_tool(self):
170
- try:
171
- #center ncbi on current screen
172
- if GlobalSettings.mainWindow.ncbi.first_show == True:
173
- GlobalSettings.mainWindow.ncbi.first_show = False
174
- GlobalSettings.mainWindow.ncbi.centerUI()
175
- if self.orgName.text() != "":
176
- GlobalSettings.mainWindow.ncbi.organism_line_edit.setText(self.orgName.text())
177
- if self.strainName.text() != "":
178
- GlobalSettings.mainWindow.ncbi.infra_name_line_edit.setText(self.strainName.text())
179
- GlobalSettings.mainWindow.ncbi.show()
180
- GlobalSettings.mainWindow.ncbi.activateWindow()
181
- except Exception as e:
182
- show_error("Error in open_ncbi_tool() in New Genome.", e)
183
-
184
- def remove_from_queue(self):
185
- try:
186
- while(True):
187
- indexes = self.job_Table.selectionModel().selectedRows()
188
- if len(indexes) == 0:
189
- break
190
- self.job_Table.removeRow(indexes[0].row())
191
- except Exception as e:
192
- show_error("Error in remove_from_queue() in New Genome.", e)
193
-
194
- #prompt user with file browser to select fasta/fna files
195
- def selectFasta(self):
196
- try:
197
- filed = QtWidgets.QFileDialog()
198
- myFile = QtWidgets.QFileDialog.getOpenFileName(filed, "Choose a File")
199
- if (myFile[0] != ""):
200
- if not myFile[0].endswith(".fa") and not myFile[0].endswith(".fna") and not myFile[0].endswith(".fasta"):
201
- show_message(
202
- fontSize=12,
203
- icon=QtWidgets.QMessageBox.Icon.Critical,
204
- title="File Selection Error",
205
- message="You have selected an incorrect type of file. Please choose a FASTA/FNA file."
206
- )
207
- return
208
- else:
209
- self.file = myFile[0]
210
- self.selectedFile.setText(str(myFile[0]))
211
- except Exception as e:
212
- show_error("Error in selectFasta() in New Genome.", e)
213
-
214
- #submit jobs to queue
215
- def submit(self):
216
- try:
217
- warning = ""
218
- if len(self.orgName.text()) == 0:
219
- warning = warning + "You need to include the organism's name."
220
- if len(self.file) == 0:
221
- warning = warning + "You need to select a file."
222
- if len(warning) != 0:
223
- show_message(
224
- fontSize=12,
225
- icon=QtWidgets.QMessageBox.Icon.Critical,
226
- title="Required Information",
227
- message=warning
228
- )
229
- return
230
- if len(self.strainName.text()) == 0:
231
- warning = warning + "\nIt is recommended to include the organism's subspecies/strain."
232
- if len(self.orgCode.text()) == 0:
233
- warning = warning + "\nYou must include an organism code."
234
- if len(warning) != 0:
235
- msgBox = QtWidgets.QMessageBox()
236
- msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
237
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
238
- msgBox.setWindowTitle("Missing Information")
239
- msgBox.setText(warning + "\n\nDo you wish to continue without including this information?")
240
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
241
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
242
- msgBox.exec()
243
-
244
- if msgBox.result() == QtWidgets.QMessageBox.No:
245
- return
246
-
247
- #endo, pam, repeats, directionality, five length, seed length, three length, orgcode, output path, CASPERinfo path, fna path, orgName, notes, on target matrix
248
- args = self.Endos[self.comboBoxEndo.currentText()][0]
249
- args += " " + self.Endos[self.comboBoxEndo.currentText()][1]
250
- if self.mt.isChecked():
251
- args += " " + "TRUE"
252
- else:
253
- args += " " + "FALSE"
254
-
255
- if self.Endos[self.comboBoxEndo.currentText()][5] == "3":
256
- args += " " + "FALSE"
257
- else:
258
- args += " " + "TRUE"
259
-
260
- if self.repeats_box.isChecked():
261
- args += " " + "TRUE"
262
- else:
263
- args += " " + "FALSE"
264
-
265
- args += " " + self.Endos[self.comboBoxEndo.currentText()][2]
266
- args += " " + self.Endos[self.comboBoxEndo.currentText()][3]
267
- args += " " + self.Endos[self.comboBoxEndo.currentText()][4]
268
- args += " " + self.orgCode.text()
269
- if platform.system() == 'Windows':
270
- args += " " + '"' + GlobalSettings.CSPR_DB.replace("/","\\") + '\\"'
271
- args += " " + '"' + GlobalSettings.appdir.replace("/","\\") + "CASPERinfo" + '"'
272
- args += " " + '"' + self.file.replace("/","\\") + '"'
273
- else:
274
- args += " " + '"' + GlobalSettings.CSPR_DB.replace("\\","/") + '/"'
275
- args += " " + '"' + GlobalSettings.appdir.replace("\\","/") + "CASPERinfo" + '"'
276
- args += " " + '"' + self.file.replace("\\","/") + '"'
277
-
278
- args += " " + '"' + self.orgName.text() + " " + self.strainName.text() + '"'
279
- args += " " + '"' + "notes" + '"'
280
- args += " " + '"DATA:' + self.Endos[self.comboBoxEndo.currentText()][6] + '"'
281
-
282
- tmp = self.orgName.text()+ " " + self.strainName.text() + " " + self.Endos[self.comboBoxEndo.currentText()][0] + " " + self.orgCode.text()
283
- if tmp in self.check_strings:
284
- show_message(
285
- fontSize=12,
286
- icon=QtWidgets.QMessageBox.Icon.Critical,
287
- title="Duplicate Entry",
288
- message="You have submitted a duplicate entry. Consider changing the organism code or strain name to differentiate closely related strains."
289
- )
290
- return
291
- name = self.orgCode.text() + "_" + str(self.Endos[self.comboBoxEndo.currentText()][0])
292
- rowPosition = self.job_Table.rowCount()
293
- self.job_Table.insertRow(rowPosition)
294
- item = QtWidgets.QTableWidgetItem(name)
295
- item.setTextAlignment(QtCore.Qt.AlignHCenter)
296
- self.job_Table.setItem(rowPosition, 0, item)
297
- self.check_strings.append(tmp)
298
- self.JobsQueue.append(args)
299
- except Exception as e:
300
- show_error("Error in submit() in New Genome.", e)
301
-
302
- #fill the endo dropdown
303
- def fillEndo(self):
304
- try:
305
- #disconnect signal
306
- try:
307
- self.comboBoxEndo.currentIndexChanged.disconnect()
308
- except:
309
- pass
310
-
311
- #clear out the endo box
312
- self.comboBoxEndo.clear()
313
-
314
- f = open(GlobalSettings.appdir + "CASPERinfo")
315
- while True:
316
- line = f.readline()
317
- if line.startswith('ENDONUCLEASES'):
318
- while True:
319
- line = f.readline()
320
- if (line[0] == "-"):
321
- break
322
- line_tokened = line.split(";")
323
- if len(line_tokened) == 10:
324
- endo = line_tokened[0]
325
- # Checking to see if there is more than one pam sequence in the list
326
- if line_tokened[1].find(",") != -1:
327
- p_pam = line_tokened[1].split(",")[0]
328
- else:
329
- p_pam = line_tokened[1]
330
- five_length = line_tokened[2]
331
- seed_length = line_tokened[3]
332
- three_length = line_tokened[4]
333
- dir = line_tokened[5]
334
- on_target_data = line_tokened[8]
335
- self.Endos[endo + " - PAM: " + p_pam] = (endo, p_pam, five_length, seed_length, three_length, dir, on_target_data)
336
- break
337
- f.close()
338
- self.comboBoxEndo.addItems(self.Endos.keys())
339
- key = list(self.Endos.keys())[0]
340
- self.seed_length.setText(self.Endos[key][3])
341
- self.five_length.setText(self.Endos[key][2])
342
- self.three_length.setText(self.Endos[key][4])
343
-
344
- #reconnect signal
345
- self.comboBoxEndo.currentIndexChanged.connect(self.changeEndos)
346
- except Exception as e:
347
- show_error("Error in fillEndo() in New Genome.", e)
348
-
349
- #event handler for endo changing - update endo length data
350
- def changeEndos(self):
351
- try:
352
- key = str(self.comboBoxEndo.currentText())
353
- self.seed_length.setText(self.Endos[key][3])
354
- self.five_length.setText(self.Endos[key][2])
355
- self.three_length.setText(self.Endos[key][4])
356
- except Exception as e:
357
- show_error("Error in changeEndos() in New Genome.", e)
358
-
359
- #check if endo is 3' or 5'
360
- def endo_settings(self):
361
- try:
362
- # check the if it's 3' or 5', and check the box accordingly
363
- if int(self.seqTrans.endo_info[self.Endos[self.comboBoxEndo.currentText()][0]][3]) == 3:
364
- self.pamBox.setChecked(0)
365
- elif int(self.seqTrans.endo_info[self.Endos[self.comboBoxEndo.currentText()][0]][3]) == 5:
366
- self.pamBox.setChecked(1)
367
- except Exception as e:
368
- show_error("Error in endo_settings() in New Genome.", e)
369
-
370
- #wrapper for running jobs
371
- def run_jobs_wrapper(self):
372
- try:
373
- self.indexes = []
374
- self.job_Table.selectAll()
375
- indexes = self.job_Table.selectionModel().selectedRows()
376
- for index in sorted(indexes):
377
- if self.job_Table.item(index.row(), 0).text() != "":
378
- self.indexes.append(index.row())
379
- self.run_job()
380
- except Exception as e:
381
- show_error("Error in run_jobs_wrapper() in New Genome.", e)
382
-
383
- #run job in queue
384
- def run_job(self):
385
- try:
386
- if len(self.indexes) > 0:
387
- self.progressBar.setValue(0)
388
- self.progress = 0
389
- row_index = self.indexes[0]
390
- name = self.job_Table.item(row_index, 0).text()
391
- item = QtWidgets.QTableWidgetItem(name)
392
- item.setTextAlignment(QtCore.Qt.AlignHCenter)
393
- self.job_Table.setItem(row_index, 1, item)
394
- self.job_Table.setItem(row_index, 0, QtWidgets.QTableWidgetItem(""))
395
-
396
- def output_stdout(p):
397
- line = str(p.readAll())
398
- line = line[2:]
399
- line = line[:len(line) - 1]
400
- for lines in line.split(r"\n"):
401
- lines = lines.rstrip("\n")
402
- lines = lines.rstrip("\r")
403
- lines = lines.rstrip(r"\n")
404
- lines = lines.rstrip(r"\r")
405
- lines = lines.rstrip("\r\n")
406
- lines = lines.rstrip(r"\r\n")
407
- if lines != "":
408
- if lines.find("Number of Chromosomes/Scaffolds") != -1:
409
- copy = lines
410
- copy = copy.replace(" ","")
411
- copy = copy[copy.find(":")+1:]
412
- self.total_chrom_count = int(copy)
413
- self.perc_increase = ((1 / (2 * self.total_chrom_count)) * 70)
414
- self.progressBar.setValue(20)
415
- self.progress = 20
416
- elif lines.find("complete.") != -1:
417
- self.progress += self.perc_increase
418
- self.progressBar.setValue(int(self.progress))
419
- elif lines.find("Processing Targets.") != -1:
420
- self.progress = 70
421
- self.progressBar.setValue(int(self.progress))
422
- elif lines.find("Writing out uniques.") != -1:
423
- self.progress = 90
424
- self.progressBar.setValue(int(self.progress))
425
- elif lines.find("Writing out repeats.") != -1:
426
- self.progress = 95
427
- self.progressBar.setValue(int(self.progress))
428
- elif lines == "Finished.":
429
- self.progress = 100
430
- self.progressBar.setValue(int(self.progress))
431
- self.output_browser.append(lines)
432
-
433
- job_args = self.JobsQueue[row_index]
434
- if platform.system() == 'Windows':
435
- program = '"' + GlobalSettings.appdir + "SeqFinderFolder/Casper_Seq_Finder_Win.exe" + '" '
436
- elif platform.system() == 'Linux':
437
- program = '"' + GlobalSettings.appdir + "SeqFinderFolder/Casper_Seq_Finder_Lin" + '" '
438
- else:
439
- program = '"' + GlobalSettings.appdir + "SeqFinderFolder/Casper_Seq_Finder_Mac" + '" '
440
- program += job_args
441
- self.process.readyReadStandardOutput.connect(partial(output_stdout, self.process))
442
- self.process.start(program)
443
- else:
444
- show_message(
445
- fontSize=12,
446
- icon=QtWidgets.QMessageBox.Icon.Critical,
447
- title="No Jobs To Run",
448
- message="No jobs are in the queue to run. Please add a job before running."
449
- )
450
- except Exception as e:
451
- show_error("Error in run_job() in New Genome.", e)
452
-
453
- #even handler for when jobs finish execution
454
- def upon_process_finishing(self):
455
- try:
456
- row_index = self.indexes[0]
457
- name = self.job_Table.item(row_index, 1).text()
458
- item = QtWidgets.QTableWidgetItem(name)
459
- item.setTextAlignment(QtCore.Qt.AlignHCenter)
460
- self.job_Table.setItem(row_index, 2, item)
461
- self.job_Table.setItem(row_index, 1, QtWidgets.QTableWidgetItem(""))
462
- self.indexes.pop(0)
463
- if len(self.indexes) != 0:
464
- self.run_job()
465
- else:
466
- #prompt user if they want to analyze their new files
467
- center_ui(self.goToPrompt)
468
- self.goToPrompt.show()
469
- self.goToPrompt.activateWindow()
470
- except Exception as e:
471
- show_error("Error in upon_process_finishing() in New Genome.", e)
472
-
473
- #clear the job table
474
- def clear_all(self):
475
- try:
476
- self.process.kill()
477
- self.fin_index = 0
478
- self.job_Table.clearContents()
479
- self.job_Table.setRowCount(0)
480
- self.JobsQueue = []
481
- self.check_strings = []
482
- self.output_browser.clear()
483
- self.output_browser.setText("Waiting for program initiation...")
484
- self.orgName.clear()
485
- self.strainName.clear()
486
- self.orgCode.clear()
487
- self.selectedFile.clear()
488
- self.selectedFile.setPlaceholderText("Selected FASTA/FNA File")
489
- self.progressBar.setValue(0)
490
- self.first = False
491
- except Exception as e:
492
- show_error("Error in clear_all() in New Genome.", e)
493
-
494
- #reset the whole form
495
- def reset(self):
496
- try:
497
- self.orgName.clear()
498
- self.strainName.clear()
499
- self.orgCode.clear()
500
- self.selectedFile.clear()
501
- self.selectedFile.setPlaceholderText("Selected FASTA/FNA File")
502
- self.output_browser.clear()
503
- self.output_browser.setText("Waiting for program initiation...")
504
- self.file = ""
505
- except Exception as e:
506
- show_error("Error in reset() in New Genome.", e)
507
-
508
- #event handler for user wanting to close the window
509
- def closeEvent(self, event):
510
- try:
511
- # make sure that there are cspr files in the DB
512
- file_names = os.listdir(GlobalSettings.CSPR_DB)
513
- noCSPRFiles = True
514
- for file in file_names:
515
- if 'cspr' in file:
516
- noCSPRFiles = False
517
- break
518
- if noCSPRFiles == True:
519
- if self.exit == False:
520
- msgBox = QtWidgets.QMessageBox()
521
- msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
522
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
523
- msgBox.setWindowTitle("No CSPR file generated")
524
- msgBox.setText("No CSPR file has been generated, thus the main program cannot run. Please create a CSPR file."
525
- "Alternatively, you could quit the program. Would you like to quit?")
526
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
527
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
528
- msgBox.exec()
529
-
530
- if (msgBox.result() == QtWidgets.QMessageBox.No):
531
- event.ignore()
532
- else:
533
- event.accept()
534
- else:
535
- self.exit = False
536
- event.accept()
537
- else:
538
- self.process.kill()
539
- self.clear_all()
540
- self.goToPrompt.hide()
541
- GlobalSettings.mainWindow.fill_annotation_dropdown()
542
- if GlobalSettings.mainWindow.orgChoice.currentText() != '':
543
- GlobalSettings.mainWindow.orgChoice.currentIndexChanged.disconnect()
544
- GlobalSettings.mainWindow.orgChoice.clear()
545
- GlobalSettings.mainWindow.endoChoice.clear()
546
- GlobalSettings.mainWindow.getData()
547
- GlobalSettings.MTWin.launch()
548
- GlobalSettings.pop_Analysis.launch()
549
-
550
- if GlobalSettings.mainWindow.first_show == True:
551
- GlobalSettings.mainWindow.first_show = False
552
- GlobalSettings.mainWindow.centerUI()
553
- GlobalSettings.mainWindow.show()
554
- event.accept()
555
- except Exception as e:
556
- show_error("Error in closeEvent() in New Genome.", e)
557
-
558
- #event handler for user wanting to go to Main once jobs complete
559
- def continue_to_main(self):
560
- try:
561
- # make sure that there are cspr files in the DB
562
- file_names = os.listdir(GlobalSettings.CSPR_DB)
563
- noCSPRFiles = True
564
- for file in file_names:
565
- if 'cspr' in file:
566
- noCSPRFiles = False
567
- break
568
- if noCSPRFiles == True:
569
-
570
- msgBox = QtWidgets.QMessageBox()
571
- msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
572
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
573
- msgBox.setWindowTitle("No CSPR file generated")
574
- msgBox.setText(
575
- "No CSPR file has been generated, thus the main program cannot run. Please create a CSPR file."
576
- "Alternatively, you could quit the program. Would you like to quit?")
577
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
578
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
579
- msgBox.exec()
580
-
581
- if (msgBox.result() == QtWidgets.QMessageBox.Yes):
582
- self.exit = True
583
- self.close()
584
- else:
585
- self.process.kill()
586
- self.clear_all()
587
- self.goToPrompt.hide()
588
- GlobalSettings.mainWindow.fill_annotation_dropdown()
589
- if GlobalSettings.mainWindow.orgChoice.currentText() != '':
590
- GlobalSettings.mainWindow.orgChoice.currentIndexChanged.disconnect()
591
- GlobalSettings.mainWindow.orgChoice.clear()
592
- GlobalSettings.mainWindow.endoChoice.clear()
593
- GlobalSettings.mainWindow.getData()
594
- GlobalSettings.MTWin.launch()
595
- GlobalSettings.pop_Analysis.launch()
596
-
597
- # center main on current screen
598
- if GlobalSettings.mainWindow.first_show == True:
599
- GlobalSettings.mainWindow.first_show = False
600
- center_ui(GlobalSettings.mainWindow)
601
- GlobalSettings.mainWindow.show()
602
- self.hide()
603
- except Exception as e:
604
- show_error("Error in continue_to_main() in New Genome.", e)
605
-
606
- #event handler for user wanting to go to multi-targeting once jobs complete
607
- def continue_to_MT(self):
608
- try:
609
- # make sure that there are cspr files in the DB
610
- file_names = os.listdir(GlobalSettings.CSPR_DB)
611
- noCSPRFiles = True
612
- for file in file_names:
613
- if 'cspr' in file:
614
- noCSPRFiles = False
615
- break
616
- if noCSPRFiles == True:
617
-
618
- msgBox = QtWidgets.QMessageBox()
619
- msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
620
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
621
- msgBox.setWindowTitle("No CSPR file generated")
622
- msgBox.setText(
623
- "No CSPR file has been generated, thus the main program cannot run. Please create a CSPR file."
624
- "Alternatively, you could quit the program. Would you like to quit?")
625
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
626
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
627
- msgBox.exec()
628
-
629
-
630
-
631
- if (msgBox.result() == QtWidgets.QMessageBox.Yes):
632
- self.exit = True
633
- self.close()
634
-
635
- else:
636
- self.process.kill()
637
- self.clear_all()
638
- self.goToPrompt.hide()
639
- GlobalSettings.mainWindow.fill_annotation_dropdown()
640
- if GlobalSettings.mainWindow.orgChoice.currentText() != '':
641
- GlobalSettings.mainWindow.orgChoice.currentIndexChanged.disconnect()
642
- GlobalSettings.mainWindow.orgChoice.clear()
643
- GlobalSettings.mainWindow.endoChoice.clear()
644
- GlobalSettings.mainWindow.getData()
645
- GlobalSettings.MTWin.launch()
646
- GlobalSettings.pop_Analysis.launch()
647
-
648
- # center multi-targeting on current screen
649
- if GlobalSettings.MTWin.first_show == True:
650
- GlobalSettings.MTWin.first_show = False
651
- GlobalSettings.MTWin.centerUI()
652
-
653
- GlobalSettings.MTWin.show()
654
- self.hide()
655
- except Exception as e:
656
- show_error("Error in continue_to_MT() in New Genome.", e)
657
-
658
- #event handler for user wanting to go to population analysis once jobs complete
659
- def continue_to_pop(self):
660
- try:
661
- # make sure that there are cspr files in the DB
662
- file_names = os.listdir(GlobalSettings.CSPR_DB)
663
- noCSPRFiles = True
664
- for file in file_names:
665
- if 'cspr' in file:
666
- noCSPRFiles = False
667
- break
668
- if noCSPRFiles == True:
669
-
670
- msgBox = QtWidgets.QMessageBox()
671
- msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
672
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
673
- msgBox.setWindowTitle("No CSPR file generated")
674
- msgBox.setText(
675
- "No CSPR file has been generated, thus the main program cannot run. Please create a CSPR file."
676
- "Alternatively, you could quit the program. Would you like to quit?")
677
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
678
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
679
- msgBox.exec()
680
-
681
- if (msgBox.result() == QtWidgets.QMessageBox.Yes):
682
- self.exit = True
683
- self.close()
684
-
685
- else:
686
- self.process.kill()
687
- self.clear_all()
688
- self.goToPrompt.hide()
689
- GlobalSettings.mainWindow.fill_annotation_dropdown()
690
- if GlobalSettings.mainWindow.orgChoice.currentText() != '':
691
- GlobalSettings.mainWindow.orgChoice.currentIndexChanged.disconnect()
692
- GlobalSettings.mainWindow.orgChoice.clear()
693
- GlobalSettings.mainWindow.endoChoice.clear()
694
- GlobalSettings.mainWindow.getData()
695
- GlobalSettings.MTWin.launch()
696
- GlobalSettings.pop_Analysis.launch()
697
-
698
- if GlobalSettings.pop_Analysis.first_show == True:
699
- GlobalSettings.pop_Analysis.first_show = False
700
- GlobalSettings.pop_Analysis.centerUI()
701
-
702
- GlobalSettings.pop_Analysis.show()
703
- self.hide()
704
- except Exception as e:
705
- show_error("Error in continue_to_pop() in New Genome.", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/PopulationAnalysisWindowView.py CHANGED
@@ -1,130 +1,138 @@
1
  from PyQt6 import QtWidgets, uic, QtGui, QtCore
2
- from PyQt6.QtWidgets import QHeaderView
3
  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
4
  from matplotlib.figure import Figure
5
  import mplcursors
6
  import numpy as np
7
  import matplotlib.patches as patches
8
- from utils.ui import show_error, scale_ui, center_ui
9
 
10
  class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
11
  def __init__(self, global_settings):
12
- super(PopulationAnalysisWindowView, self).__init__()
13
- self.global_settings = global_settings
 
14
  self.init_ui()
15
 
16
  def init_ui(self):
17
  try:
18
- uic.loadUi(self.global_settings.get_ui_dir() + '/pop.ui', self)
19
- self.setWindowIcon(QtGui.QIcon(self.global_settings.get_assets_dir() + "/cas9image.ico"))
20
- self.setWindowTitle('Population Analysis')
21
- self.setup_tables()
22
- self.setup_buttons()
23
- self.setup_colormap()
24
- self.setup_styles()
25
- scale_ui(self, base_width=1920, base_height=1080, font_size=12, header_font_size=30)
26
  except Exception as e:
27
- show_error(self.global_settings, "Error initializing PopulationAnalysisWindowView.", str(e))
28
-
29
- def setup_tables(self):
30
- # Organism table
31
- self.org_Table.setColumnCount(1)
32
- self.org_Table.setShowGrid(False)
33
- self.org_Table.setHorizontalHeaderLabels(["Organism"])
34
- self.org_Table.horizontalHeader().setSectionsClickable(True)
35
- self.org_Table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
36
- self.org_Table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
37
- self.org_Table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
38
- self.org_Table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.MultiSelection)
39
-
40
- # Shared seeds table
41
- self.table2.setColumnCount(9)
42
- self.table2.setShowGrid(False)
43
- self.table2.setHorizontalHeaderLabels(["Seed","% Coverage","Total Repeats","Avg. Repeats/Scaffold", "Consensus Sequence", "% Consensus", "Score","PAM", "Strand"])
44
- self.table2.horizontalHeader().setSectionsClickable(True)
45
- self.table2.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
46
- self.table2.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
47
- self.table2.setSelectionBehavior(QtWidgets.QTableView.SelectionBehavior.SelectRows)
48
- self.table2.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.MultiSelection)
49
- self.table2.resizeColumnsToContents()
50
-
51
- # Location finder table
52
- self.loc_finder_table.setColumnCount(5)
53
- self.loc_finder_table.setShowGrid(False)
54
- self.loc_finder_table.setHorizontalHeaderLabels(["Seed ID", "Sequence", "Organism", "Scaffold", "Location"])
55
- self.loc_finder_table.horizontalHeader().setSectionsClickable(True)
56
- self.loc_finder_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
57
- self.loc_finder_table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
58
- self.loc_finder_table.resizeColumnsToContents()
59
-
60
- def setup_buttons(self):
61
- self.goBackButton.setText("Go Back")
62
- self.analyze_button.setText("Analyze")
63
- self.clear_Button.setText("Clear")
64
- self.export_button.setText("Export")
65
- self.find_locs_button.setText("Find Locations")
66
- self.clear_loc_button.setText("Clear Locations")
67
- self.query_seed_button.setText("Search Seeds")
68
-
69
- def setup_colormap(self):
70
- self.colormap_layout = QtWidgets.QVBoxLayout()
71
- self.colormap_layout.setContentsMargins(0, 0, 0, 0)
72
- self.colormap_canvas = MplCanvas(self)
73
- self.colormap_layout.addWidget(self.colormap_canvas)
74
- self.colormap_figure.setLayout(self.colormap_layout)
75
-
76
- def setup_styles(self):
77
- groupbox_style = """
78
- QGroupBox:title{subcontrol-origin: margin;
79
- left: 10px;
80
- padding: 0 5px 0 15px;}
81
- QGroupBox#groupBox{border: 2px solid rgb(111,181,110);
82
- border-radius: 9px;
83
- font: bold 14pt 'Arial';
84
- margin-top: 10px;}"""
85
- self.groupBox.setStyleSheet(groupbox_style)
86
- self.groupBox_2.setStyleSheet(groupbox_style.replace("groupBox","groupBox_2"))
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  def get_selected_endo(self):
89
- return self.endoBox.currentText()
90
 
91
  def get_selected_organisms(self):
92
  return [index.row() for index in self.org_Table.selectionModel().selectedRows()]
93
 
94
  def get_selected_seeds(self):
95
- return [self.table2.item(index.row(), 0).text() for index in self.table2.selectionModel().selectedRows()]
96
 
97
  def get_seed_input(self):
98
  return self.seed_input.text()
99
 
100
  def get_selected_seeds_for_export(self):
101
- return [item.text() for item in self.table2.selectedItems() if item.column() == 0]
102
 
103
  def update_org_table(self, org_data):
104
- self.org_Table.setRowCount(len(org_data))
105
  for row, (org_name, cspr_file, db_file) in enumerate(org_data):
106
  item = QtWidgets.QTableWidgetItem(org_name)
107
  item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignVCenter)
108
- self.org_Table.setItem(row, 0, item)
109
- self.org_Table.resizeColumnsToContents()
110
 
111
  def update_shared_seeds_table(self, seed_data):
112
- self.table2.setRowCount(len(seed_data))
113
  for row, data in enumerate(seed_data):
 
114
  for col, value in enumerate(data):
115
  item = QtWidgets.QTableWidgetItem(str(value))
116
  item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
117
- self.table2.setItem(row, col, item)
118
- self.table2.resizeColumnsToContents()
119
 
120
  def update_loc_finder_table(self, loc_data):
121
- self.loc_finder_table.setRowCount(len(loc_data))
122
  for row, data in enumerate(loc_data):
 
123
  for col, key in enumerate(['seed', 'sequence', 'organism', 'chromosome', 'location']):
124
  item = QtWidgets.QTableWidgetItem(str(data[key]))
125
  item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
126
- self.loc_finder_table.setItem(row, col, item)
127
- self.loc_finder_table.resizeColumnsToContents()
128
 
129
  def plot_heatmap(self, data, labels):
130
  self.colormap_canvas.axes.clear()
@@ -159,28 +167,35 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
159
  self.colormap_canvas.draw()
160
 
161
  def clear_shared_seeds_table(self):
162
- self.table2.setRowCount(0)
163
 
164
  def clear_loc_finder_table(self):
165
  self.loc_finder_table.setRowCount(0)
166
 
167
- def show_loading_window(self, value):
168
- self.loading_window.loading_bar.setValue(value)
169
- self.loading_window.show()
170
-
171
- def hide_loading_window(self):
172
- self.loading_window.hide()
173
-
174
- def update_loading_window(self, value, text):
175
- self.loading_window.loading_bar.setValue(value)
176
- self.loading_window.info_label.setText(text)
177
-
178
  def update_endo_dropdown(self, endos):
179
- self.endoBox.clear()
180
- self.endoBox.addItems(endos)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  def sort_table2(self, column):
183
- self.table2.sortItems(column)
184
 
185
  def sort_loc_finder_table(self, column):
186
  self.loc_finder_table.sortItems(column)
@@ -194,12 +209,3 @@ class MplCanvas(FigureCanvasQTAgg):
194
  super(MplCanvas, self).__init__(fig)
195
  except Exception as e:
196
  show_error("Error initializing MplCanvas class in population analysis.", e)
197
-
198
- class LoadingWindow(QtWidgets.QMainWindow):
199
- def __init__(self, global_settings):
200
- super(LoadingWindow, self).__init__()
201
- uic.loadUi(global_settings.get_ui_dir() + "/loading_data_form.ui", self)
202
- self.loading_bar.setValue(0)
203
- self.setWindowTitle("Loading Data")
204
- self.setWindowIcon(QtGui.QIcon(global_settings.get_assets_dir() + "cas9image.ico"))
205
- scale_ui(self, base_width=1920, base_height=1080, font_size=12, header_font_size=30, custom_scale_width=450, custom_scale_height=125)
 
1
  from PyQt6 import QtWidgets, uic, QtGui, QtCore
2
+ from PyQt6.QtWidgets import QHeaderView, QAbstractItemView
3
  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
4
  from matplotlib.figure import Figure
5
  import mplcursors
6
  import numpy as np
7
  import matplotlib.patches as patches
8
+ from utils.ui import show_error, scale_ui
9
 
10
  class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
11
  def __init__(self, global_settings):
12
+ super().__init__()
13
+ self.settings = global_settings
14
+ self.logger = self.settings.get_logger()
15
  self.init_ui()
16
 
17
  def init_ui(self):
18
  try:
19
+ uic.loadUi(self.settings.get_ui_dir_path() + '/population_analysis.ui', self)
20
+ self._init_ui_components()
 
 
 
 
 
 
21
  except Exception as e:
22
+ show_error(self.settings, "Error initializing PopulationAnalysisWindowView", str(e))
23
+
24
+ def _init_ui_components(self):
25
+ self._init_grpSelectOrganisms()
26
+ self._init_grpSeedAnalysis()
27
+ # self._init_colormap()
28
+
29
+ def _init_grpSelectOrganisms(self):
30
+ self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
31
+ print(self.combo_box_endonuclease)
32
+ self.table_organism = self._find_widget('tblOrganism', QtWidgets.QTableWidget)
33
+ self.push_button_analyze_organism = self._find_widget('pbtnAnalyzeOrganism', QtWidgets.QPushButton)
34
+
35
+ self.tab_widget_shared_seeds_heatmap = self._find_widget('tabsSharedSeedHeatmap', QtWidgets.QTabWidget)
36
+ self.tab_shared_seed_heatmap = self._find_widget('tabSharedSeedHeatmap', QtWidgets.QWidget)
37
+ self.heatmap_seed = self._find_widget('heatmapSeed', QtWidgets.QWidget)
38
+
39
+ self.table_organism.setColumnCount(1)
40
+ self.table_organism.setShowGrid(False)
41
+ self.table_organism.setHorizontalHeaderLabels(["Organism"])
42
+ self.table_organism.horizontalHeader().setSectionsClickable(True)
43
+ self.table_organism.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
44
+ self.table_organism.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
45
+ self.table_organism.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
46
+ self.table_organism.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
47
+
48
+ def _init_grpSeedAnalysis(self):
49
+ self.line_edit_seed = self._find_widget('ledSeed', QtWidgets.QLineEdit)
50
+ self.push_button_query_seed = self._find_widget('pbtnQuerySeed', QtWidgets.QPushButton)
51
+ self.push_button_clear_seeds = self._find_widget('pbtnClearSeeds', QtWidgets.QPushButton)
52
+ self.table_seed = self._find_widget('tblSeed', QtWidgets.QTableWidget)
53
+
54
+ self.table_seed.setColumnCount(9)
55
+ self.table_seed.setShowGrid(False)
56
+ self.table_seed.setHorizontalHeaderLabels([
57
+ "Seed", "% Coverage", "Total Repeats", "Avg. Repeats/Scaffold",
58
+ "Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"
59
+ ])
60
+ self.table_seed.horizontalHeader().setSectionsClickable(True)
61
+ self.table_seed.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
62
+ self.table_seed.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
63
+ self.table_seed.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
64
+
65
+ self.push_button_find_locations = self._find_widget('pbtnFindLocations', QtWidgets.QPushButton)
66
+ self.push_button_clear_locations = self._find_widget('pbtnClearLocations', QtWidgets.QPushButton)
67
+
68
+ self.table_locations = self._find_widget('tblLocation', QtWidgets.QTableWidget)
69
+
70
+ self.table_locations.setColumnCount(5)
71
+ self.table_locations.setShowGrid(False)
72
+ self.table_locations.setHorizontalHeaderLabels([
73
+ "Seed ID", "Sequence", "Organism", "Scaffold", "Location"
74
+ ])
75
+ self.table_locations.horizontalHeader().setSectionsClickable(True)
76
+ self.table_locations.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
77
+ self.table_locations.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
78
+
79
+ # def _init_colormap(self):
80
+ # self.colormap_figure = self._find_widget('wgtColormap', QtWidgets.QWidget)
81
+ # if self.colormap_figure:
82
+ # self.colormap_layout = QtWidgets.QVBoxLayout()
83
+ # self.colormap_layout.setContentsMargins(0, 0, 0, 0)
84
+ # self.colormap_canvas = MplCanvas(self)
85
+ # self.colormap_layout.addWidget(self.colormap_canvas)
86
+ # self.colormap_figure.setLayout(self.colormap_layout)
87
+
88
+ def _find_widget(self, name: str, widget_type: type) -> QtWidgets.QWidget:
89
+ widget = self.findChild(widget_type, name)
90
+ if widget is None:
91
+ self.logger.warning(f"Widget '{name}' not found in UI file.")
92
+ return widget
93
 
94
  def get_selected_endo(self):
95
+ return self.combo_box_endonuclease.currentText()
96
 
97
  def get_selected_organisms(self):
98
  return [index.row() for index in self.org_Table.selectionModel().selectedRows()]
99
 
100
  def get_selected_seeds(self):
101
+ return [self.table_seed.item(index.row(), 0).text() for index in self.table_seed.selectionModel().selectedRows()]
102
 
103
  def get_seed_input(self):
104
  return self.seed_input.text()
105
 
106
  def get_selected_seeds_for_export(self):
107
+ return [item.text() for item in self.table_seed.selectedItems() if item.column() == 0]
108
 
109
  def update_org_table(self, org_data):
110
+ self.table_organism.setRowCount(len(org_data))
111
  for row, (org_name, cspr_file, db_file) in enumerate(org_data):
112
  item = QtWidgets.QTableWidgetItem(org_name)
113
  item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignVCenter)
114
+ self.table_organism.setItem(row, 0, item)
115
+ self.table_organism.resizeColumnsToContents()
116
 
117
  def update_shared_seeds_table(self, seed_data):
118
+ self.table_seed.setRowCount(len(seed_data))
119
  for row, data in enumerate(seed_data):
120
+ print(data)
121
  for col, value in enumerate(data):
122
  item = QtWidgets.QTableWidgetItem(str(value))
123
  item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
124
+ self.table_seed.setItem(row, col, item)
125
+ self.table_seed.resizeColumnsToContents()
126
 
127
  def update_loc_finder_table(self, loc_data):
128
+ self.table_locations.setRowCount(len(loc_data))
129
  for row, data in enumerate(loc_data):
130
+ print(data)
131
  for col, key in enumerate(['seed', 'sequence', 'organism', 'chromosome', 'location']):
132
  item = QtWidgets.QTableWidgetItem(str(data[key]))
133
  item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
134
+ self.table_locations.setItem(row, col, item)
135
+ self.table_locations.resizeColumnsToContents()
136
 
137
  def plot_heatmap(self, data, labels):
138
  self.colormap_canvas.axes.clear()
 
167
  self.colormap_canvas.draw()
168
 
169
  def clear_shared_seeds_table(self):
170
+ self.table_seed.setRowCount(0)
171
 
172
  def clear_loc_finder_table(self):
173
  self.loc_finder_table.setRowCount(0)
174
 
 
 
 
 
 
 
 
 
 
 
 
175
  def update_endo_dropdown(self, endos):
176
+ """Update the endonuclease dropdown with the provided options"""
177
+ try:
178
+ self.logger.info("Starting update_endo_dropdown")
179
+ self.logger.debug(f"Received endos: {endos}")
180
+
181
+ print(self.combo_box_endonuclease)
182
+
183
+ # if not self.combo_box_endonuclease:
184
+ # self.logger.error("combo_box_endonuclease is None")
185
+ # return
186
+
187
+ self.combo_box_endonuclease.clear()
188
+ self.combo_box_endonuclease.addItems(endos)
189
+
190
+ self.logger.info(f"Updated endonuclease dropdown with {len(endos)} options")
191
+ self.logger.debug(f"Current items in dropdown: {[self.combo_box_endonuclease.itemText(i) for i in range(self.combo_box_endonuclease.count())]}")
192
+ except Exception as e:
193
+ self.logger.error(f"Error updating endonuclease dropdown: {str(e)}")
194
+ self.logger.exception("Full traceback:")
195
+ show_error(self.settings, "Error updating endonuclease dropdown", str(e))
196
 
197
  def sort_table2(self, column):
198
+ self.table_seed.sortItems(column)
199
 
200
  def sort_loc_finder_table(self, column):
201
  self.loc_finder_table.sortItems(column)
 
209
  super(MplCanvas, self).__init__(fig)
210
  except Exception as e:
211
  show_error("Error initializing MplCanvas class in population analysis.", e)
 
 
 
 
 
 
 
 
 
src/views/ViewTargetsView.py CHANGED
@@ -61,24 +61,63 @@ class ViewTargetsView(QtWidgets.QMainWindow):
61
  return widget
62
 
63
  def display_targets_in_table(self, targets):
 
64
  self.table_targets.setRowCount(len(targets))
 
65
  for row, target in enumerate(targets):
66
- self.table_targets.setItem(row, 0, QTableWidgetItem(str(target[0]))) # Location
67
- self.table_targets.setItem(row, 1, QTableWidgetItem(target[5])) # Endonuclease
68
- self.table_targets.setItem(row, 2, QTableWidgetItem(target[1])) # Sequence
69
- self.table_targets.setItem(row, 3, QTableWidgetItem(target[4])) # Strand
70
- self.table_targets.setItem(row, 4, QTableWidgetItem(target[2])) # PAM
71
- self.table_targets.setItem(row, 5, QTableWidgetItem(str(target[3]))) # Score
72
- self.table_targets.setItem(row, 6, QTableWidgetItem("N/A")) # Off-Target (placeholder)
 
 
 
 
 
 
 
 
 
 
 
73
 
 
74
  details_button = QtWidgets.QPushButton("Details")
75
  self.table_targets.setCellWidget(row, 7, details_button)
76
-
77
  self.table_targets.resizeColumnsToContents()
78
 
79
  def get_selected_targets(self):
 
80
  selected_rows = set(index.row() for index in self.table_targets.selectedIndexes())
81
- return [self.get_row_data(row) for row in selected_rows]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  def get_row_data(self, row):
84
  return {
 
61
  return widget
62
 
63
  def display_targets_in_table(self, targets):
64
+ """Display targets in table with all data"""
65
  self.table_targets.setRowCount(len(targets))
66
+
67
  for row, target in enumerate(targets):
68
+ # Handle tuple format (location, sequence, pam, score, strand, endonuclease)
69
+ if isinstance(target, tuple):
70
+ self.table_targets.setItem(row, 0, QTableWidgetItem(str(target[0]))) # Location
71
+ self.table_targets.setItem(row, 1, QTableWidgetItem(str(target[5]))) # Endonuclease
72
+ self.table_targets.setItem(row, 2, QTableWidgetItem(str(target[1]))) # Sequence
73
+ self.table_targets.setItem(row, 3, QTableWidgetItem(str(target[4]))) # Strand
74
+ self.table_targets.setItem(row, 4, QTableWidgetItem(str(target[2]))) # PAM
75
+ self.table_targets.setItem(row, 5, QTableWidgetItem(str(target[3]))) # Score
76
+ self.table_targets.setItem(row, 6, QTableWidgetItem("--.--")) # Off-Target placeholder
77
+ # Handle dictionary format
78
+ else:
79
+ self.table_targets.setItem(row, 0, QTableWidgetItem(str(target['location'])))
80
+ self.table_targets.setItem(row, 1, QTableWidgetItem(str(target['endonuclease'])))
81
+ self.table_targets.setItem(row, 2, QTableWidgetItem(str(target['sequence'])))
82
+ self.table_targets.setItem(row, 3, QTableWidgetItem(str(target['strand'])))
83
+ self.table_targets.setItem(row, 4, QTableWidgetItem(str(target['pam'])))
84
+ self.table_targets.setItem(row, 5, QTableWidgetItem(str(target['score'])))
85
+ self.table_targets.setItem(row, 6, QTableWidgetItem("--.--"))
86
 
87
+ # Add details button
88
  details_button = QtWidgets.QPushButton("Details")
89
  self.table_targets.setCellWidget(row, 7, details_button)
90
+
91
  self.table_targets.resizeColumnsToContents()
92
 
93
  def get_selected_targets(self):
94
+ """Get selected targets with all necessary data"""
95
  selected_rows = set(index.row() for index in self.table_targets.selectedIndexes())
96
+ selected_targets = []
97
+
98
+ # Get column indices once
99
+ columns = {
100
+ 'location': 0,
101
+ 'endonuclease': 1,
102
+ 'sequence': 2, # Make sure to get the sequence
103
+ 'strand': 3,
104
+ 'pam': 4,
105
+ 'score': 5,
106
+ 'off_target': 6
107
+ }
108
+
109
+ for row in selected_rows:
110
+ target = {
111
+ 'location': self.table_targets.item(row, columns['location']).text(),
112
+ 'endonuclease': self.table_targets.item(row, columns['endonuclease']).text(),
113
+ 'sequence': self.table_targets.item(row, columns['sequence']).text(), # Get sequence
114
+ 'strand': self.table_targets.item(row, columns['strand']).text(),
115
+ 'pam': self.table_targets.item(row, columns['pam']).text(),
116
+ 'score': self.table_targets.item(row, columns['score']).text(),
117
+ 'off_target': self.table_targets.item(row, columns['off_target']).text()
118
+ }
119
+ selected_targets.append(target)
120
+ return selected_targets
121
 
122
  def get_row_data(self, row):
123
  return {
src/views/closingWin.py DELETED
@@ -1,73 +0,0 @@
1
- import models.GlobalSettings as GlobalSettings
2
- import os
3
- from PyQt5 import QtWidgets, Qt, uic
4
- import traceback
5
- import math
6
- from utils.ui import show_error, scale_ui
7
-
8
- logger = GlobalSettings.logger
9
-
10
- ###########################################################
11
- # closingWindow: this class is a little window where the user can select which files they want to delete
12
- # Once they hit 'submit' it will delete all of the files selected, and close the program.
13
- # If no files are selected, the program closes and no files are deleted
14
- # Inputs are taking from the user (selecting files to delete and hitting submit), as well as GlobalSettings for the files in CSPR_DB
15
- # Outputs are the files are deleting, and the program is closed
16
- ###########################################################
17
- class closingWindow(QtWidgets.QMainWindow):
18
- def __init__(self):
19
- try:
20
- super(closingWindow, self).__init__()
21
- uic.loadUi(GlobalSettings.appdir + "ui/closing_window.ui", self)
22
- self.setWindowTitle("Delete Files")
23
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
24
-
25
- # Button
26
- self.submit_button.clicked.connect(self.submit_and_close)
27
-
28
- # Table
29
- self.files_table.setColumnCount(1)
30
- self.files_table.setShowGrid(True)
31
- self.files_table.setHorizontalHeaderLabels("File Name;".split(";"))
32
- self.files_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
33
- self.files_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
34
- self.files_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
35
-
36
-
37
- scale_ui(self, custom_scale_width=400, custom_scale_height=300)
38
-
39
-
40
- except Exception as e:
41
- show_error("Error initializing closingWindow class.", e)
42
-
43
- # this function will delete selected files, and then close the program
44
- def submit_and_close(self):
45
- try:
46
- # loop through the whole table
47
- for i in range(self.files_table.rowCount()):
48
- tabWidget = self.files_table.item(i, 0)
49
-
50
- # if that specific tab is selected, delete it. otherwise do nothing
51
- if tabWidget.isSelected():
52
- os.remove(tabWidget.text())
53
- self.close()
54
- except Exception as e:
55
- show_error("Error in sumbit_and_close() in closing window.", e)
56
-
57
- # this function gets all of the files from the CSPR_DB and puts them all into the table
58
- def get_files(self):
59
- try:
60
- loopCount = 0
61
- # get the file names from CSPR_DB
62
- files_names = os.listdir(GlobalSettings.CSPR_DB)
63
- files_names.sort(key=str.lower)
64
- self.files_table.setRowCount(len(files_names))
65
-
66
- # loop through and add them to the table
67
- for file in files_names:
68
- tabWidget = QtWidgets.QTableWidgetItem(file)
69
- self.files_table.setItem(loopCount, 0, tabWidget)
70
- loopCount += 1
71
- self.files_table.resizeColumnsToContents()
72
- except Exception as e:
73
- show_error("Error in get_files() in closing window.", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/export_tool.py DELETED
@@ -1,259 +0,0 @@
1
- import models.GlobalSettings as GlobalSettings
2
- from utils.sequence_utils import get_table_headers
3
- import os
4
- from PyQt5 import QtWidgets, Qt, uic, QtCore, QtGui
5
- import platform
6
- import traceback
7
- import math
8
- from utils.ui import show_message, show_error, scale_ui, center_ui
9
-
10
- logger = GlobalSettings.logger
11
-
12
- # This class opens a window for the user to select where they want the CSV file exported to, and the name of the file
13
- # It takes the highlighted data from the Results page, and creates a CSV file from that
14
- class export_tool(QtWidgets.QMainWindow):
15
- def __init__(self):
16
- try:
17
- super(export_tool, self).__init__()
18
- uic.loadUi(GlobalSettings.appdir + 'ui/export_tool.ui', self)
19
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
20
-
21
- self.browse_button.clicked.connect(self.browseForFolder)
22
- self.cancel_button.clicked.connect(self.cancel_function)
23
- self.export_button.clicked.connect(self.export_function)
24
-
25
- # Set up validators for input fields:
26
- reg_ex = QtCore.QRegExp("[^,]+") # No commas
27
- input_validator = QtGui.QRegExpValidator(reg_ex, self)
28
- self.leading_seq.setValidator(input_validator)
29
- self.trailing_seq.setValidator(input_validator)
30
-
31
- # GroupBox styling
32
- groupbox_style = """
33
- QGroupBox:title{subcontrol-origin: margin;
34
- left: 10px;
35
- padding: 0 5px 0 5px;}
36
- QGroupBox#gRNA_Options{border: 2px solid rgb(111,181,110);
37
- border-radius: 9px;
38
- margin-top: 10px;
39
- font: bold 14pt 'Arial';} """
40
- self.gRNA_Options.setStyleSheet(groupbox_style)
41
-
42
- self.location = self.fileLocation_line_edit.text()
43
- self.selected_table_items = []
44
- self.window = ""
45
- self.num_columns = []
46
- self.locus_tag = False
47
- self.gene_name = False
48
-
49
- self.setWindowTitle("Export to CSV")
50
- scale_ui(self, custom_scale_width=650, custom_scale_height=200)
51
-
52
- except Exception as e:
53
- show_error("Error initializing export_tool class.", e)
54
-
55
- # launch function. Called in Results.
56
- # parameter expect: a list of the items selected from the window.
57
- def launch(self, select_items, window):
58
- try:
59
- if platform.system() == "Windows":
60
- self.fileLocation_line_edit.setText(GlobalSettings.CSPR_DB + "\\")
61
- else:
62
- self.fileLocation_line_edit.setText(GlobalSettings.CSPR_DB + "/")
63
- self.selected_table_items = select_items
64
- self.window = window
65
- center_ui(self)
66
- self.show()
67
- self.activateWindow()
68
- except Exception as e:
69
- show_error("Error in launch() in export_tool.", e)
70
-
71
- # Takes the path and file name and combines them
72
- # Writes the header line, as well as ever line selected to that file
73
- # calls the cancel function when it's done
74
- def export_function(self):
75
- try:
76
- delim = self.delimBox.currentText()
77
- # get the full path ( path and file name)
78
- file_name = self.filename_line_edit.text()
79
- if file_name == "":
80
- file_name = "exported_gRNAs"
81
- self.location = self.fileLocation_line_edit.text()
82
- full_path = ""
83
- if '.' in file_name: # If user added the file extension...
84
- full_path = self.location + file_name
85
- else:
86
- if delim == ",":
87
- full_path = self.location + file_name + '.csv'
88
- elif delim == r"\t":
89
- delim = "\t"
90
- full_path = self.location + file_name + '.tsv'
91
- else:
92
- full_path = self.location + file_name + '.txt'
93
- try:
94
- output_data = open(full_path, 'w')
95
- """ Write the table headers """
96
- if self.window == "mt": ###Change headers for multitargeting table export
97
- headers = get_table_headers(GlobalSettings.MTWin.table)
98
- num_cols = len(headers) # Calculate the number of columns based on the headers list above
99
- insertion_index = headers.index("% Consensus")
100
- headers.insert(insertion_index, "Full Sequence")
101
- output_data.write(delim.join(headers)+"\n")
102
- elif self.window == "pa":
103
- headers = get_table_headers(GlobalSettings.pop_Analysis.table2)
104
- num_cols = len(headers) # Calculate the number of columns based on the headers list above
105
- insertion_index = headers.index("% Consensus")
106
- headers.insert(insertion_index, "Full Sequence")
107
- output_data.write(delim.join(headers)+"\n")
108
- else: ###Change headers for view results export
109
- headers = get_table_headers(GlobalSettings.mainWindow.Results.targetTable)
110
- headers.remove("Details") # For some reason, the details column doesn't carry any "items"
111
- num_cols = len(headers) # Calculate the number of columns based on the headers list above
112
- insertion_index = headers.index("Strand")
113
- headers.insert(insertion_index, "Full Sequence")
114
-
115
- if GlobalSettings.mainWindow.radioButton_Gene.isChecked(): # If the user chose to search via Feature
116
- tmp = GlobalSettings.mainWindow.Results.comboBoxGene.currentText().split(":") # Check to see if the locus tag was found for the current gene
117
- if len(tmp) > 1: # If locus tag exists for gene, include in output
118
- headers.extend(["Locus_Tag","Gene_Name"])
119
- output_data.write(delim.join(headers)+"\n")
120
- self.locus_tag = True
121
- self.gene_name = True
122
- else: # If locus tag does not exist for gene, only include the gene name
123
- headers.append("Gene_Name")
124
- output_data.write(delim.join(headers)+"\n")
125
- self.gene_name = True
126
- self.locus_tag = False
127
- else: # If user searched by sequence or position, don't include locus tag or gene name
128
- output_data.write(delim.join(headers)+"\n")
129
- self.gene_name = False
130
- self.locus_tag = False
131
-
132
- """ Write the data out """
133
- tmp_list = []
134
- if self.locus_tag: #If the user is exporting data from VT and locus tag exists for current gene
135
- tmp = GlobalSettings.mainWindow.Results.comboBoxGene.currentText().split(":") # Get the locus tag
136
- locus_tag = str(tmp[0].strip())
137
- gene_name = str(tmp[-1].strip())
138
- seq_index = headers.index("Sequence") # Get the gene name
139
- it = 0
140
- for i, item in enumerate(self.selected_table_items): # Loop through all the items in the View Targets table
141
- if (i+1) % num_cols == 0:
142
- tmp_list.append(item.text())
143
- tmp_list.append(locus_tag)
144
- tmp_list.append(gene_name)
145
- output_data.write(delim.join(tmp_list)+"\n") # Write data out
146
- tmp_list.clear() # Reset list
147
- it = 0 # Reset iterator
148
- elif it == seq_index:
149
- tmp_list.append(item.text())
150
- tmp_list.append(self.leading_seq.text().strip() + item.text() + self.trailing_seq.text().strip()) #5' Leader + gRNA + 3' Trailer
151
- it += 1
152
- else:
153
- tmp_list.append(item.text())
154
- it += 1
155
- elif self.gene_name: #If the user is exporting data from VT and locus tag doesn't exist for current gene
156
- gene_name = str(GlobalSettings.mainWindow.Results.comboBoxGene.currentText().strip()) # Get the locus tag
157
- seq_index = headers.index("Sequence") # Get the gene name
158
- it = 0
159
- for i, item in enumerate(self.selected_table_items): # Loop through all the items in the View Targets table
160
- if (i+1) % num_cols == 0:
161
- tmp_list.append(item.text())
162
- tmp_list.append(gene_name)
163
- output_data.write(delim.join(tmp_list)+"\n")
164
- tmp_list.clear()
165
- it = 0 # Reset iterator
166
- elif it == seq_index:
167
- tmp_list.append(item.text())
168
- tmp_list.append(self.leading_seq.text().strip() + item.text() + self.trailing_seq.text().strip()) #5' Leader + gRNA + 3' Trailer
169
- it += 1
170
- else:
171
- tmp_list.append(item.text())
172
- it += 1
173
- elif self.window in ["mt", "pa"]: #If the user is exporting data from multitargeting
174
- seq_index = headers.index("Consensus Sequence")
175
- it = 0
176
- for i, item in enumerate(self.selected_table_items): # Loop through all the items in the View Targets table
177
- if (i+1) % num_cols == 0:
178
- tmp_list.append(item.text())
179
- output_data.write(str(delim.join(tmp_list))+"\n")
180
- tmp_list.clear()
181
- it = 0 # Reset iterator
182
- elif it == seq_index:
183
- tmp_list.append(item.text())
184
- tmp_list.append(self.leading_seq.text().strip() + item.text() + self.trailing_seq.text().strip()) #5' Leader + gRNA + 3' Trailer
185
- it += 1
186
- else:
187
- tmp_list.append(item.text())
188
- it += 1
189
- else: #If the user is exporting data from View Targets but is not using Feature search
190
- seq_index = headers.index("Sequence") # Get the gene name
191
- it = 0
192
- for i, item in enumerate(self.selected_table_items): # Loop through all the items in the View Targets table
193
- if (i+1) % num_cols == 0:
194
- tmp_list.append(item.text())
195
- output_data.write(delim.join(tmp_list)+"\n")
196
- tmp_list.clear()
197
- it = 0 # Reset iterator
198
- elif it == seq_index:
199
- tmp_list.append(item.text())
200
- tmp_list.append(self.leading_seq.text().strip() + item.text() + self.trailing_seq.text().strip()) #5' Leader + gRNA + 3' Trailer
201
- it += 1
202
- else:
203
- tmp_list.append(item.text())
204
- it += 1
205
- output_data.close()
206
- except PermissionError:
207
- show_error("This file cannot be opened. Please make sure that the file is not opened elsewhere and try again.", e)
208
- return
209
-
210
- except Exception as e:
211
- show_error("Error in export_function() in export_tool.", e)
212
- return
213
-
214
- """ Print "finished" message """
215
- show_message(
216
- fontSize=12,
217
- icon=QtWidgets.QMessageBox.Icon.Information,
218
- title="Export Complete",
219
- message=f"Export to {full_path} was successful."
220
- )
221
-
222
- # close the window
223
- self.cancel_function()
224
- except Exception as e:
225
- show_error("Error in export_function() in export_tool.", e)
226
-
227
- # Resets everything to the init funciton
228
- # then closes the window
229
- def cancel_function(self):
230
- try:
231
- if platform.system() == "Windows":
232
- self.fileLocation_line_edit.setText(GlobalSettings.CSPR_DB + "\\")
233
- else:
234
- self.fileLocation_line_edit.setText(GlobalSettings.CSPR_DB + "/")
235
- self.filename_line_edit.setText("")
236
- self.location = ""
237
- self.hide()
238
- except Exception as e:
239
- show_error("Error in cancel_function() in export_tool.", e)
240
-
241
- # browse for folder function
242
- # allows user to browse for a folder where to store the CSV file
243
- def browseForFolder(self):
244
- try:
245
- # get the folder
246
- filed = QtWidgets.QFileDialog()
247
- mydir = QtWidgets.QFileDialog.getExistingDirectory(filed, "Open a Folder",
248
- GlobalSettings.CSPR_DB, QtWidgets.QFileDialog.ShowDirsOnly)
249
- if(os.path.isdir(mydir) == False):
250
- return
251
-
252
- if platform.system() == "Windows":
253
- self.fileLocation_line_edit.setText(mydir + "\\")
254
- self.location = mydir + "\\"
255
- else:
256
- self.fileLocation_line_edit.setText(mydir + "/")
257
- self.location = mydir + "/"
258
- except Exception as e:
259
- show_error("Error in browseForFolder() in export_tool.", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/generateLib.py DELETED
@@ -1,662 +0,0 @@
1
- import models.GlobalSettings as GlobalSettings
2
- import os
3
- from PyQt5 import QtWidgets, Qt, uic, QtCore
4
- from functools import partial
5
- from models.CSPRparser import CSPRparser
6
- import re
7
- import platform
8
- import traceback
9
- import math
10
- from utils.ui import show_message, show_error, scale_ui, center_ui
11
- from views.annotation_functions import *
12
-
13
- logger = GlobalSettings.logger
14
-
15
- # this class is a window that allows the user to select the settings for Generate Library
16
- # When the user clicks Generate Library, it goes ahead and gets the Annotation Data needed
17
- # Then the user can select the settings they want, and then hit submit.
18
- # It creates a txt file with the data
19
- class genLibrary(QtWidgets.QMainWindow):
20
- def __init__(self):
21
- try:
22
- super(genLibrary, self).__init__()
23
- uic.loadUi(GlobalSettings.appdir + 'ui/generate_library.ui', self)
24
- self.setWindowTitle('Generate Library')
25
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + 'cas9image.ico'))
26
-
27
- groupbox_style = """
28
- QGroupBox:title{subcontrol-origin: margin;
29
- left: 10px;
30
- padding: 0 5px 0 5px;}
31
- QGroupBox#Step1{border: 2px solid rgb(111,181,110);
32
- border-radius: 9px;
33
- font: bold 14pt 'Arial';
34
- margin-top: 10px;}"""
35
- self.Step1.setStyleSheet(groupbox_style)
36
- self.Step2.setStyleSheet(groupbox_style.replace("Step1", "Step2"))
37
- self.Step3.setStyleSheet(groupbox_style.replace("Step1", "Step3"))
38
- self.Step4.setStyleSheet(groupbox_style.replace("Step1", "Step4"))
39
-
40
- self.cancel_button.clicked.connect(self.cancel_function)
41
- self.BrowseButton.clicked.connect(self.browse_function)
42
- self.submit_button.clicked.connect(self.submit_data)
43
- self.progressBar.setValue(0)
44
-
45
- self.anno_data = dict()
46
- self.kegg_nonKegg = ''
47
- self.gen_lib_dict = dict()
48
- self.cspr_data = dict()
49
- self.Output = dict()
50
- self.off_tol = .05
51
- self.off_max_misMatch = 4
52
- self.off_target_running = False
53
- self.parser = CSPRparser("")
54
-
55
- # set the numbers for the num genes combo box item
56
- for i in range(10):
57
- self.numGenescomboBox.addItem(str(i + 1))
58
-
59
- # set the numbers for the minOn combo box
60
- for i in range(19, 70):
61
- self.minON_comboBox.addItem(str(i + 1))
62
-
63
- scale_ui(self, custom_scale_width=950, custom_scale_height=500)
64
-
65
- except Exception as e:
66
- show_error("Error initializing generate library class.", e)
67
-
68
- # this function launches the window
69
- # Parameters:
70
- # annotation_data: a dictionary that has the data for the annotations searched for
71
- # currently MainWindow's searches dict is passed into this
72
- # org_file: the cspr_file that pertains to the organism that user is using at the time
73
- # anno_type: whether the user is using KEGG or another type of annotation file
74
- def launch(self, annotation_data, org_file, anno_type):
75
- try:
76
- self.cspr_file = org_file
77
- self.db_file = org_file[:org_file.find('.')] + '_repeats.db'
78
- self.anno_data = annotation_data
79
- self.kegg_nonKegg = anno_type
80
- self.process = QtCore.QProcess()
81
- self.parser.fileName = org_file
82
-
83
- # setting the path and file name fields
84
- index1 = self.cspr_file.find('.')
85
- if platform.system() == "Windows":
86
- index2 = self.cspr_file.rfind('\\')
87
- else:
88
- index2 = self.cspr_file.rfind('/')
89
-
90
- self.filename_input.setText(self.cspr_file[index2 + 1:index1] + '_lib')
91
-
92
-
93
- if platform.system() == "Windows":
94
- self.output_path.setText(GlobalSettings.CSPR_DB + "\\")
95
- else:
96
- self.output_path.setText(GlobalSettings.CSPR_DB + "/")
97
-
98
- # depending on the type of file, build the dictionary accordingly
99
- self.build_dict_non_kegg()
100
-
101
- # get the gRNA data from the cspr file
102
- self.cspr_data = self.parser.gen_lib_parser(self.gen_lib_dict, GlobalSettings.mainWindow.endoChoice.currentText())
103
- self.get_endo_data()
104
-
105
- center_ui(self)
106
- self.show()
107
- self.activateWindow()
108
- except Exception as e:
109
- show_error("Error in launch() in generate library.", e)
110
-
111
- def get_endo_data(self):
112
- try:
113
- f = open(GlobalSettings.appdir + "CASPERinfo")
114
- self.endo_data = {}
115
- while True:
116
- line = f.readline()
117
- if line.startswith('ENDONUCLEASES'):
118
- while True:
119
- line = f.readline()
120
- line = line.replace("\n","")
121
- if (line[0] == "-"):
122
- break
123
- line_tokened = line.split(";")
124
- if len(line_tokened) == 10:
125
- endo = line_tokened[0]
126
- five_length = line_tokened[2]
127
- seed_length = line_tokened[3]
128
- three_length = line_tokened[4]
129
- prime = line_tokened[5]
130
- hsu = line_tokened[9]
131
- self.endo_data[endo] = [int(five_length) + int(three_length) + int(seed_length), prime, "MATRIX:" + hsu]
132
-
133
- break
134
- f.close()
135
- except Exception as e:
136
- show_error("Error in get_endo_data() in generate library.", e)
137
-
138
- # this is here in case the user clicks 'x' instead of cancel. Just calls the cancel function
139
- def closeEvent(self, event):
140
- try:
141
- closeWindow = self.cancel_function()
142
-
143
- # if the user is doing OT and does not decide to cancel it ignore the event
144
- if closeWindow == -2:
145
- event.ignore()
146
- else:
147
- event.accept()
148
- except Exception as e:
149
- show_error("Error in closeEvent() in generate library.", e)
150
-
151
- # this function takes all of the cspr data and compresses it again for off-target usage
152
- def compress_file_off(self):
153
- try:
154
- if platform.system() == "Windows":
155
- file = GlobalSettings.CSPR_DB + "\\off_input.txt"
156
- else:
157
- file = GlobalSettings.CSPR_DB + "/off_input.txt"
158
- f = open(file, 'w')
159
- for gene in self.cspr_data:
160
- for j in range(len(self.cspr_data[gene])):
161
- loc = self.cspr_data[gene][j][0]
162
- seq = self.cspr_data[gene][j][1]
163
- pam = self.cspr_data[gene][j][2]
164
- score = self.cspr_data[gene][j][3]
165
- strand = self.cspr_data[gene][j][4]
166
- output = str(loc) + ';' + str(seq) + ';' + str(pam) + ';' + str(score) + ';' + str(strand)
167
- f.write(output + '\n')
168
- f.close()
169
- except Exception as e:
170
- show_error("Error in compress_file_off() in generate library.", e)
171
-
172
- # this function parses the temp_off file, which holds the off-target analysis results
173
- # it also updates each target in the cspr_data dictionary to replace the endo with the target's results in off-target
174
- def parse_off_file(self):
175
- try:
176
- if platform.system() == "Windows":
177
- file = GlobalSettings.CSPR_DB + "\\temp_off.txt"
178
- else:
179
- file = GlobalSettings.CSPR_DB + "/temp_off.txt"
180
- f = open(file, "r")
181
- file_data = f.read().split('\n')
182
- f.close()
183
- scoreDict = dict()
184
-
185
- # get the data from the file
186
- for i in range(len(file_data)):
187
- if file_data[i] == 'AVG OUTPUT':
188
- continue
189
- elif file_data[i] != '':
190
- buffer = file_data[i].split(':')
191
- scoreDict[buffer[0]] = buffer[1]
192
-
193
- # update cspr_Data
194
- for gene in self.cspr_data:
195
- for i in range(len(self.cspr_data[gene])):
196
- tempTuple = (self.cspr_data[gene][i][0], self.cspr_data[gene][i][1], self.cspr_data[gene][i][2], self.cspr_data[gene][i][3], self.cspr_data[gene][i][4], scoreDict[self.cspr_data[gene][i][1]])
197
- self.cspr_data[gene][i] = tempTuple
198
- except Exception as e:
199
- show_error("Error in parse_off_file() in generate library.", e)
200
-
201
- # this function runs the off_target command
202
- # NOTE: some changes may be needed to get it to work with other OS besides windows
203
- def get_offTarget_data(self, num_targets, minScore, spaceValue, output_file, fiveseq):
204
- try:
205
- self.perc = False
206
- self.bool_temp = False
207
- self.running = False
208
-
209
- # when finished, parse the off file, and then generate the lib
210
- def finished():
211
- if self.off_target_running:
212
- self.progressBar.setValue(100)
213
- self.parse_off_file()
214
- did_work = self.generate(num_targets, minScore, spaceValue, output_file, fiveseq)
215
- self.off_target_running = False
216
- #self.process.kill()
217
- if did_work != -1:
218
- self.cancel_function()
219
- show_message(
220
- fontSize=12,
221
- icon=QtWidgets.QMessageBox.Icon.Information,
222
- title="Library Generated!",
223
- message="CASPER has finished generating your library!"
224
- )
225
- os.remove(GlobalSettings.CSPR_DB + '/off_input.txt')
226
- os.remove(GlobalSettings.CSPR_DB + '/temp_off.txt')
227
-
228
- # as off-targeting outputs things, update the off-target progress bar
229
- def progUpdate(p):
230
- line = str(self.process.readAllStandardOutput())
231
- line = line[2:]
232
- line = line[:len(line) - 1]
233
- if platform.system() == 'Windows':
234
- for lines in filter(None, line.split(r'\r\n')):
235
- if (lines.find("Running Off Target Algorithm for") != -1 and self.perc == False):
236
- self.perc = True
237
- if (self.perc == True and self.bool_temp == False and lines.find(
238
- "Running Off Target Algorithm for") == -1):
239
- lines = lines[32:]
240
- lines = lines.replace("%", "")
241
- if (float(lines) <= 99.5):
242
- num = float(lines)
243
- self.progressBar.setValue(num)
244
- else:
245
- self.bool_temp = True
246
- else:
247
- for lines in filter(None, line.split(r'\n')):
248
- if (lines.find("Running Off Target Algorithm for") != -1 and self.perc == False):
249
- self.perc = True
250
- if (self.perc == True and self.bool_temp == False and lines.find(
251
- "Running Off Target Algorithm for") == -1):
252
- lines = lines[32:]
253
- lines = lines.replace("%", "")
254
- if (float(lines) <= 99.5):
255
- num = float(lines)
256
- self.progressBar.setValue(num)
257
- else:
258
- self.bool_temp = True
259
-
260
- if platform.system() == 'Windows':
261
- app_path = GlobalSettings.appdir
262
- exe_path = app_path + 'OffTargetFolder\\OT_Win.exe'
263
- output_path = '"' + GlobalSettings.CSPR_DB + '\\temp_off.txt" '
264
- data_path = '"' + GlobalSettings.CSPR_DB + "\\off_input.txt" + '" '
265
- elif platform.system() == 'Linux':
266
- app_path = GlobalSettings.appdir.replace('\\', '/')
267
- exe_path = app_path + r'OffTargetFolder/OT_Lin'
268
- output_path = '"' + GlobalSettings.CSPR_DB + '/temp_off.txt" '
269
- data_path = '"' + GlobalSettings.CSPR_DB + "/off_input.txt" + '" '
270
- else:
271
- app_path = GlobalSettings.appdir.replace('\\', '/')
272
- exe_path = app_path + r'OffTargetFolder/OT_Mac'
273
- output_path = '"' + GlobalSettings.CSPR_DB + '/temp_off.txt" '
274
- data_path = '"' + GlobalSettings.CSPR_DB + "/off_input.txt" + '" '
275
- exe_path = '"' + exe_path + '" '
276
- cspr_path = '"' + self.cspr_file + '" '
277
- db_path = '"' + self.db_file + '" '
278
- filename = output_path
279
- filename = filename[:len(filename) - 1]
280
- filename = filename[1:]
281
- filename = filename.replace('"', '')
282
- CASPER_info_path = '"' + app_path + 'CASPERinfo' +'" '
283
- num_of_mismathes = self.off_max_misMatch
284
- tolerance = self.off_tol # create command string
285
- endo = '"' + GlobalSettings.mainWindow.endoChoice.currentText() + '" '
286
- detailed_output = " False "
287
- avg_output = "True"
288
- hsu = ' "' + self.endo_data[GlobalSettings.mainWindow.endoChoice.currentText()][2] + '"'
289
-
290
- # set the off_target_running to true, to keep the user from closing the window while it is running
291
- self.off_target_running = True
292
-
293
- cmd = exe_path + data_path + endo + cspr_path + db_path + output_path + CASPER_info_path + str(
294
- num_of_mismathes) + ' ' + str(tolerance) + detailed_output + avg_output + hsu
295
-
296
- if platform.system() == 'Windows':
297
- cmd = cmd.replace('/', '\\')
298
- self.process.readyReadStandardOutput.connect(partial(progUpdate, self.process))
299
- self.process.readyReadStandardError.connect(partial(progUpdate, self.process))
300
- self.progressBar.setValue(0)
301
- QtCore.QTimer.singleShot(100, partial(self.process.start, cmd))
302
- self.process.finished.connect(finished)
303
- except Exception as e:
304
- show_error("Error in get_offTarget_data() in generate library.", e)
305
-
306
- # submit function
307
- # this function takes all of the input from the window, and calls the generate function
308
- # Still need to add the checks for 5' seq, and the percentage thing
309
- def submit_data(self):
310
- try:
311
- if self.off_target_running:
312
- return
313
- output_file = self.output_path.text() + self.filename_input.text()
314
-
315
- minScore = int(self.minON_comboBox.currentText())
316
- num_targets = int(self.numGenescomboBox.currentText())
317
- fiveseq = ''
318
-
319
- # error check for csv files
320
- if output_file.endswith('.txt'):
321
- output_file = output_file.replace('.txt', '.csv')
322
- elif not output_file.endswith('.txt') and not output_file.endswith('.csv'):
323
- output_file = output_file + '.csv'
324
-
325
- # error checking for the space value
326
- # if they enter nothing, default to 15 and also make sure it's actually a digit
327
- if self.space_line_edit.text() == '':
328
- spaceValue = 15
329
- elif self.space_line_edit.text().isdigit():
330
- spaceValue = int(self.space_line_edit.text())
331
- elif not self.space_line_edit.text().isdigit():
332
- show_message(
333
- fontSize=12,
334
- icon=QtWidgets.QMessageBox.Icon.Critical,
335
- title="Error",
336
- message="Please enter integers only for space between guides."
337
- )
338
- return
339
- # if space value is more than 200, default to 200
340
- if spaceValue > 200:
341
- spaceValue = 200
342
- elif spaceValue < 0:
343
- show_message(
344
- fontSize=12,
345
- icon=QtWidgets.QMessageBox.Icon.Critical,
346
- title="Error",
347
- message="Please enter a space-value that is 0 or greater."
348
- )
349
- return
350
-
351
- if self.find_off_Checkbox.isChecked():
352
- self.compress_file_off()
353
-
354
- # get the fiveprimseq data and error check it
355
- if self.fiveprimeseq.text() != '' and self.fiveprimeseq.text().isalpha():
356
- fiveseq = self.fiveprimeseq.text()
357
- elif self.fiveprimeseq.text() != '' and not self.fiveprimeseq.text().isalpha():
358
- show_message(
359
- fontSize=12,
360
- icon=QtWidgets.QMessageBox.Icon.Critical,
361
- title="Error",
362
- message="Please make sure only the letters A, T, G, or C are added into 5' End specificity box."
363
- )
364
- return
365
-
366
- # get the targeting range data, and error check it here
367
- if not self.start_target_range.text().isdigit() or not self.end_target_range.text().isdigit():
368
- show_message(
369
- fontSize=12,
370
- icon=QtWidgets.QMessageBox.Icon.Critical,
371
- title="Error",
372
- message="Error: Please make sure that the start and end target ranges are numbers only. Please make sure that start is 0 or greater, and end is 100 or less. "
373
- )
374
- return
375
- elif int(self.start_target_range.text()) >= int(self.end_target_range.text()):
376
- show_message(
377
- fontSize=12,
378
- icon=QtWidgets.QMessageBox.Icon.Critical,
379
- title="Error",
380
- message="Please make sure that the start number is always less than the end number"
381
- )
382
- return
383
-
384
- # if they check Off-Targeting
385
- if self.find_off_Checkbox.isChecked():
386
- # make sure its a digit
387
- if self.maxOFF_comboBox.text() == '' or not self.maxOFF_comboBox.text().isdigit() and '.' not in self.maxOFF_comboBox.text():
388
- show_message(
389
- fontSize=12,
390
- icon=QtWidgets.QMessageBox.Icon.Critical,
391
- title="Error",
392
- message="Please enter only numbers for Maximum Off-Target Score. It cannot be left blank"
393
- )
394
- return
395
- else:
396
- # make sure it between 0 and .5
397
- if not 0.0 < float(self.maxOFF_comboBox.text()) <= .5:
398
- show_message(
399
- fontSize=12,
400
- icon=QtWidgets.QMessageBox.Icon.Critical,
401
- title="Error",
402
- message="Please enter a max off-target score between 0 and 0.5!"
403
- )
404
- return
405
- # compress the data, and then run off-targeting
406
- self.compress_file_off()
407
- self.get_offTarget_data(num_targets, minScore, spaceValue, output_file, fiveseq)
408
- else:
409
- # actually call the generate function
410
- did_work = self.generate(num_targets, minScore, spaceValue, output_file, fiveseq)
411
-
412
- if did_work != -1:
413
- self.cancel_function()
414
- show_message(
415
- fontSize=12,
416
- icon=QtWidgets.QMessageBox.Icon.Critical,
417
- title="Library Generated!",
418
- message="CASPER has finished generating your library!"
419
- )
420
- except Exception as e:
421
- show_error("Error in submit_data() in generate library.", e)
422
-
423
- # clears everything and hides the window
424
- def cancel_function(self):
425
- try:
426
- if self.off_target_running:
427
- msgBox = QtWidgets.QMessageBox()
428
- msgBox.setStyleSheet("font: " + str(12) + "pt 'Arial'")
429
- msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
430
- msgBox.setWindowTitle("Off-Targeting is running")
431
- msgBox.setText(
432
- "Off-Targetting is running. Closing this window will cancel that process, and return to the main window. .\n Do you wish to continue?")
433
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
434
- msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
435
- msgBox.exec()
436
-
437
- if (msgBox.result() == QtWidgets.QMessageBox.No):
438
- return -2
439
- else:
440
- self.off_target_running = False
441
- self.process.kill()
442
-
443
- self.cspr_file = ''
444
- self.anno_data = list()
445
-
446
- self.filename_input.setText('')
447
- self.output_path.setText('')
448
-
449
- self.gen_lib_dict.clear()
450
- self.cspr_data.clear()
451
- self.Output.clear()
452
-
453
- self.start_target_range.setText('0')
454
- self.end_target_range.setText('100')
455
- self.space_line_edit.setText('15')
456
- self.find_off_Checkbox.setChecked(False)
457
- self.modifyParamscheckBox.setChecked(False)
458
- self.maxOFF_comboBox.setText('')
459
- self.fiveprimeseq.setText('')
460
- self.off_target_running = False
461
- self.progressBar.setValue(0)
462
-
463
- self.hide()
464
- except Exception as e:
465
- show_error("Error in cancel_function() in generate library.", e)
466
-
467
- # allows the user to browse for a folder
468
- # stores their selection in the output_path line edit
469
- def browse_function(self):
470
- try:
471
- if self.off_target_running:
472
- return
473
- # get the folder
474
- filed = QtWidgets.QFileDialog()
475
- mydir = QtWidgets.QFileDialog.getExistingDirectory(filed, "Open a Folder",
476
- GlobalSettings.CSPR_DB, QtWidgets.QFileDialog.ShowDirsOnly)
477
- if(os.path.isdir(mydir) == False):
478
- return
479
-
480
- # make sure to append the '/' to the folder path
481
- if platform.system() == "Windwos":
482
- self.output_path.setText(mydir + "\\")
483
- else:
484
- self.output_path.setText(mydir + "/")
485
- except Exception as e:
486
- show_error("Error in browse_function() in generate library.", e)
487
-
488
- # this function builds the dictionary that is used in the generate function
489
- # this is the version that builds it from data from feature_table, gbff, or gff
490
- # builds it exactly as Brian built it in the files given
491
- def build_dict_non_kegg(self):
492
- try:
493
- for tuple in self.anno_data:
494
- chrom = tuple[0]
495
- feature = tuple[1]
496
- feature_id = get_id(feature)
497
- feature_name = get_name(feature)
498
- feature_desc = get_description(feature)
499
- ### Order: chromosome number, gene start, gene end, dir of gene, gene description, gene name/locus tag
500
- self.gen_lib_dict[feature_name] = [chrom,int(feature.location.start),int(feature.location.end),get_strand(feature),get_description(feature),get_name(feature)]
501
- except Exception as e:
502
- show_error("Error in build_dict_non_kegg() in generate library.", e)
503
-
504
- # generate function taken from Brian's code
505
- def generate(self,num_targets_per_gene, score_limit, space, output_file, fiveseq):
506
- try:
507
- deletedDict = dict()
508
-
509
- # check and see if we need to search based on target_range
510
- startNum = float(self.start_target_range.text())
511
- endNum = float(self.end_target_range.text())
512
- checkStartandEndBool = False
513
- if startNum != 0.0 or endNum != 100.0:
514
- if startNum >= 0.0 and endNum <= 100.0:
515
- startNum = startNum / 100
516
- endNum = endNum / 100
517
- checkStartandEndBool = True
518
- else:
519
- show_message(
520
- fontSize=12,
521
- icon=QtWidgets.QMessageBox.Icon.Critical,
522
- title="Invalid Targeting Range:",
523
- message="Please select a targeting range between 0 and 100."
524
- )
525
- return -1
526
-
527
- for gene in self.gen_lib_dict:
528
- target_list = self.cspr_data[gene] # Gets the gRNAs for given gene
529
-
530
- #target_list = chrom_list[k:l+1]
531
- # Reverse the target list if the gene is on negative strand:
532
- if self.gen_lib_dict[gene][3] == "-":
533
- target_list.reverse()
534
-
535
- # Filter out the guides with low scores and long strings of T's
536
- # also store the ones deleted if the user selects 'modify search parameters'
537
- if self.modifyParamscheckBox.isChecked():
538
- deletedDict[gene] = list()
539
- for i in range(len(target_list) - 1, -1, -1): ### Start at end and move backwards through list
540
- # check the target_range here
541
- if int(target_list[i][3]) < int(score_limit):
542
- if self.modifyParamscheckBox.isChecked():
543
- deletedDict[gene].append(target_list[i])
544
- target_list.pop(i)
545
- # check for gRNAs with poly T regions here
546
- elif re.search("T{5,10}", target_list[i][1]) is not None:
547
- if self.modifyParamscheckBox.isChecked():
548
- deletedDict[gene].append(target_list[i])
549
- target_list.pop(i)
550
-
551
- # check for the fiveseq
552
- if fiveseq != '':
553
- for i in range(len(target_list) - 1, -1, -1): ### Start at end and move backwards through list
554
- if not target_list[i][1].startswith(fiveseq.upper()):
555
- if self.modifyParamscheckBox.isChecked():
556
- deletedDict[gene].append(target_list[i])
557
- target_list.pop(i)
558
- # check the target range here
559
- if checkStartandEndBool:
560
- for i in range(len(target_list) - 1, -1, -1):
561
- totalDistance = self.gen_lib_dict[gene][2] - self.gen_lib_dict[gene][1]
562
- target_loc = abs(int(target_list[i][0])) - int(self.gen_lib_dict[gene][1])
563
- myRatio = target_loc / totalDistance
564
-
565
- if not (startNum <= myRatio <= endNum):
566
- if self.modifyParamscheckBox.isChecked():
567
- deletedDict[gene].append(target_list[i])
568
- target_list.pop(i)
569
- # if the user selected off-targeting, check to see that the targets do not exceed the selected max score
570
- if self.find_off_Checkbox.isChecked():
571
- maxScore = float(self.maxOFF_comboBox.text())
572
- for i in range(len(target_list) - 1, -1, -1):
573
- if maxScore < float(target_list[i][5]):
574
- if self.modifyParamscheckBox.isChecked():
575
- deletedDict[gene].append(target_list[i])
576
- target_list.pop(i)
577
- # Now generating the targets
578
- self.Output[gene] = list()
579
- i = 0
580
- vec_index = 0
581
- prev_target = (0, "xyz", 'abc', 1, "-")
582
- while i < num_targets_per_gene:
583
- # select the first five targets with the score and space filter that is set in the beginning
584
- if len(target_list) == 0 or vec_index >= len(target_list):
585
- break
586
- while abs(int(target_list[vec_index][0]) - int(prev_target[0])) < int(space):
587
- if target_list[vec_index][3] > prev_target[3] and prev_target != (0,"xyz", "abc", 1, "-"):
588
- self.Output[gene].remove(prev_target)
589
- self.Output[gene].append(target_list[vec_index])
590
- prev_target = target_list[vec_index]
591
- vec_index += 1
592
- # check and see if there will be a indexing error
593
- if vec_index >= len(target_list) - 1:
594
- vec_index = vec_index - 1
595
- break
596
- # Add the new target to the output and add another to i
597
- self.Output[gene].append(target_list[vec_index])
598
- prev_target = target_list[vec_index]
599
- i += 1
600
- vec_index += 1
601
-
602
- # if the user selects modify search parameters, go through and check to see if each one has the number of targets that the user wanted
603
- # if not, append from the deletedDict until they do
604
- if self.modifyParamscheckBox.isChecked():
605
- for gene in self.Output:
606
- if len(self.Output[gene]) < num_targets_per_gene:
607
- for i in range(len(deletedDict[gene])):
608
- if len(self.Output[gene]) == num_targets_per_gene:
609
- break
610
- else:
611
- loc = deletedDict[gene][i][0]
612
- seq = deletedDict[gene][i][1]
613
- pam = deletedDict[gene][i][2]
614
- score = deletedDict[gene][i][3]
615
- strand = deletedDict[gene][i][4] + '*'
616
- endo = deletedDict[gene][i][5]
617
- self.Output[gene].append((loc, seq, pam, score, strand, endo))
618
-
619
- # Now output to the file
620
- try:
621
- f = open(output_file, 'w')
622
- # if OT checked
623
- if self.find_off_Checkbox.isChecked():
624
- f.write('Gene Name,Sequence,On-Target Score,Off-Target Score,Location,PAM,Strand\n')
625
- elif not self.find_off_Checkbox.isChecked():
626
- f.write('Gene Name,Sequence,On-Target Score,Location,PAM,Strand\n')
627
-
628
- for gene in self.Output:
629
- i = 0
630
- gene_name = self.gen_lib_dict[gene][-1]
631
- for target in self.Output[gene]:
632
- # check to see if the target did not match the user's parameters and they selected 'modify'
633
- # if the target has an error, put 2 asterisks in front of the target sequence
634
- if '*' in target[4]:
635
- tag_id = "**" + gene_name + "-" + str(i + 1)
636
- else:
637
- tag_id = gene_name + "-" + str(i + 1)
638
- i += 1
639
-
640
- tag_id = tag_id.replace(',', '')
641
-
642
- # if OT checked
643
- if self.find_off_Checkbox.isChecked():
644
- f.write(tag_id + ',' + target[1] + ',' + str(target[3]) + ',' + str(target[5]) + ',' + str(abs(int(target[0]))) + ',' + target[2] + ',' + target[4][0] + '\n')
645
- # if OT not checked
646
- elif not self.find_off_Checkbox.isChecked():
647
- f.write(tag_id + ',' + target[1] + ',' + str(target[3]) + ',' + str(abs(int(target[0]))) + ',' + target[2] + ',' + target[4][0] + '\n')
648
-
649
- f.close()
650
- except PermissionError:
651
- show_message(
652
- fontSize=12,
653
- icon=QtWidgets.QMessageBox.Icon.Critical,
654
- title="File Cannot Open",
655
- message="This file cannot be opened. Please make sure that the file is not opened elsewhere and try again."
656
- )
657
- return -1
658
- except Exception as e:
659
- print(e)
660
- return
661
- except Exception as e:
662
- show_error("Error in generate() in generate library.", e)